diff --git a/.ci/release/Jenkinsfile b/.ci/release/Jenkinsfile index 9d59f8a041..1000f42ddc 100644 --- a/.ci/release/Jenkinsfile +++ b/.ci/release/Jenkinsfile @@ -102,7 +102,7 @@ pipeline { """ ) dir("${BASE_DIR}") { - git credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba', url: 'git@github.com:elastic/apm-agent-java.git' + git(credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba', url: 'git@github.com:elastic/apm-agent-java.git', branch: 'main') } } } diff --git a/.ci/schedule-weekly.groovy b/.ci/schedule-weekly.groovy index aa19116e3b..d15e4c0434 100644 --- a/.ci/schedule-weekly.groovy +++ b/.ci/schedule-weekly.groovy @@ -20,15 +20,16 @@ pipeline { stages { stage('Agent weekly exhaustive test') { steps { - build(job: 'apm-agent-java/apm-agent-java-mbp/main', - parameters: [ - booleanParam(name: 'jdk_compatibility_ci', value: true), - booleanParam(name: 'end_to_end_tests_ci', value: true), - booleanParam(name: 'agent_integration_tests_ci', value: true), - ], - propagate: false, - wait: false - ) + build(job: 'apm-agent-java/apm-agent-java-mbp/main', + parameters: [ + booleanParam(name: 'jdk_compatibility_ci', value: true), + booleanParam(name: 'windows_ci', value: true), + booleanParam(name: 'end_to_end_tests_ci', value: true), + booleanParam(name: 'agent_integration_tests_ci', value: true), + ], + propagate: false, + wait: false + ) } } } diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 894db843b0..9d18b00606 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -20,8 +20,21 @@ endif::[] === Unreleased -[[release-notes-1.28.5]] -==== 1.28.5 - YYYY/MM/DD +[[release-notes-1.29.1]] +==== 1.29.1 - YYYY/MM/DD + +[float] +===== Features + +[[release-notes-1.x]] +=== Java Agent version 1.x + +[[release-notes-1.29.0]] +==== 1.29.0 - 2022/02/09 + +[float] +===== Breaking changes +* Changes in service name auto-discovery of jar files (see Features section) [float] ===== Features @@ -29,9 +42,18 @@ endif::[] * Replaced `authorization` in the default value of `sanitize_field_names` with `*auth*` - {pull}2326[#2326] * Unsampled transactions are dropped and not sent to the APM-Server if the APM-Server version is 8.0+ - {pull}2329[#2329] * Adding agent logging capabilities to our SDK, making it available for external plugins - {pull}2390[#2390] -* When the `MANIFEST.MF` of the main jar contains the `Implementation-Title` attribute, it is used as the default service name - {pull}1921[#1921] - Note: this may change your service names if you relied on the auto-discovery that uses the name of the jar file. If that jar file also contains an `Implementation-Title` attribute in the `MANIFEST.MF` file, the latter will take precedence. -* When the `MANIFEST.MF` of the main jar contains the `Implementation-Version` attribute, it is used as the default service version (except for application servers) - {pull}1922[#1922] +* Service name auto-discovery improvements +** For applications deployed to application servers (`war` files) and standalone jars that are started with `java -jar`, + the agent now discovers the `META-INF/MANIFEST.MF` file. +** If the manifest contains the `Implementation-Title` attribute, it is used as the default service name - {pull}1921[#1921], {pull}2434[#2434] + + *Note*: this may change your service names if you relied on the auto-discovery that uses the name of the jar file. + If that jar file also contains an `Implementation-Title` attribute in the `MANIFEST.MF` file, the latter will take precedence. +** When the manifest contains the `Implementation-Version` attribute, it is used as the default service version - {pull}1726[#1726], {pull}1922[#1922], {pull}2434[#2434] +* Added support for instrumenting Struts 2 static resource requests - {pull}1949[#1949] +* Added support for Java/Jakarta WebSocket ServerEndpoint - {pull}2281[#2281] +* Added support for setting the service name on Log4j2's EcsLayout - {pull}2296[#2296] +* Print the used instrumentation groups when the application stops - {pull}2448[#2448] +* Add `elastic.apm.start_async` property that makes the agent start on a non-premain/main thread - {pull}2454[#2454] [float] ===== Bug fixes @@ -44,9 +66,9 @@ paths. The BCI warmup is on by default and may be disabled through the internal ** Dubbo transaction will should be created at the provider side ** APM headers conversion issue within dubbo transaction * Fix External plugins automatic setting of span outcome - {pull}2376[#2376] - -[[release-notes-1.x]] -=== Java Agent version 1.x +* Avoid early initialization of JMX on Weblogic - {pull}2420[#2420] +* Automatically disable class sharing on AWS lambda layer - {pull}2438[#2438] +* Avoid standalone spring applications to have two different service names, one based on the jar name, the other based on `spring.application.name`. [[release-notes-1.28.4]] ==== 1.28.4 - 2021/12/30 diff --git a/Jenkinsfile b/Jenkinsfile index 4862f338b5..22a866670e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -16,6 +16,7 @@ pipeline { ITS_PIPELINE = 'apm-integration-tests-selector-mbp/main' MAVEN_CONFIG = '-Dmaven.repo.local=.m2' OPBEANS_REPO = 'opbeans-java' + JAVA_VERSION = "${params.JAVA_VERSION}" JOB_GCS_BUCKET_STASH = 'apm-ci-temp' JOB_GCS_CREDENTIALS = 'apm-ci-gcs-plugin' } @@ -30,9 +31,10 @@ pipeline { quietPeriod(10) } triggers { - issueCommentTrigger("(${obltGitHubComments()}|^run (jdk compatibility|benchmark|integration|end-to-end) tests)") + issueCommentTrigger("(${obltGitHubComments()}|^run (jdk compatibility|benchmark|integration|end-to-end|windows) tests)") } parameters { + string(name: 'JAVA_VERSION', defaultValue: 'java11', description: 'Java version to build & test') string(name: 'MAVEN_CONFIG', defaultValue: '-V -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dhttps.protocols=TLSv1.2 -Dmaven.wagon.http.retryHandler.count=3 -Dmaven.wagon.httpconnectionManager.ttlSeconds=25', description: 'Additional maven options.') // Note about GH checks and optional steps @@ -59,6 +61,10 @@ pipeline { // disabled by default, not required for merge // opt-in with 'ci:jdk-compatibility' tag on PR booleanParam(name: 'jdk_compatibility_ci', defaultValue: false, description: 'Enable JDK compatibility tests') + + // disabled by default, not required for merge + // opt-in with 'ci:windows' tag on PR + booleanParam(name: 'windows_ci', defaultValue: false, description: 'Enable Windows build & tests') } stages { stage('Checkout') { @@ -82,7 +88,7 @@ pipeline { } } } - stage('Builds'){ + stage('Builds') { options { skipDefaultCheckout() } when { // Tags are not required to be built/tested. @@ -92,19 +98,21 @@ pipeline { } environment { HOME = "${env.WORKSPACE}" - JAVA_HOME = "${env.HUDSON_HOME}/.java/java11" - PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/${env.JAVA_VERSION}" MAVEN_CONFIG = "${params.MAVEN_CONFIG} ${env.MAVEN_CONFIG}" } stages { /** - Build on a linux environment. - */ + * Build on a linux environment. + */ stage('Build') { when { beforeAgent true expression { return env.ONLY_DOCS == "false" } } + environment { + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } steps { withGithubNotify(context: 'Build', tab: 'artifacts') { deleteDir() @@ -124,9 +132,13 @@ pipeline { } stashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") archiveArtifacts allowEmptyArchive: true, - artifacts: "${BASE_DIR}/elastic-apm-agent/target/elastic-apm-agent-*.jar,${BASE_DIR}/apm-agent-attach/target/apm-agent-attach-*.jar,\ - ${BASE_DIR}/apm-agent-attach-cli/target/apm-agent-attach-cli-*.jar,${BASE_DIR}/apm-agent-api/target/apm-agent-api-*.jar,\ - ${BASE_DIR}/target/site/aggregate-third-party-report.html", + artifacts: "\ + ${BASE_DIR}/elastic-apm-agent/target/elastic-apm-agent-*.jar,\ + ${BASE_DIR}/elastic-apm-agent/target/elastic-apm-java-aws-lambda-layer-*.zip,\ + ${BASE_DIR}/apm-agent-attach/target/apm-agent-attach-*.jar,\ + ${BASE_DIR}/apm-agent-attach-cli/target/apm-agent-attach-cli-*.jar,\ + ${BASE_DIR}/apm-agent-api/target/apm-agent-api-*.jar,\ + ${BASE_DIR}/target/site/aggregate-third-party-report.html", onlyIfSuccessful: true } } @@ -139,25 +151,65 @@ pipeline { failFast true parallel { /** - Run only unit test. - */ + * Run only unit tests + */ stage('Unit Tests') { options { skipDefaultCheckout() } when { beforeAgent true expression { return params.test_ci } } + environment { + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } steps { withGithubNotify(context: 'Unit Tests', tab: 'tests') { deleteDir() - unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") - dir("${BASE_DIR}"){ + unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") + dir("${BASE_DIR}") { withOtelEnv() { - sh """#!/bin/bash - set -euxo pipefail - ./mvnw test - """ + sh label: 'mvn test', script: './mvnw test' + } + } + } + } + post { + always { + reportTestResults() + } + } + } + /** * + * Build & Test on Windows environment + */ + stage('Build & Test Windows') { + agent { label 'windows-2019-docker-immutable' } + options { skipDefaultCheckout() } + when { + beforeAgent true + allOf { + expression { return params.test_ci } + anyOf { + expression { return params.windows_ci } + expression { return env.GITHUB_COMMENT?.contains('windows tests') } + expression { matchesPrLabel(label: 'ci:windows') } + } + } + } + environment { + JAVA_HOME = "C:\\Users\\jenkins\\.java\\${env.JAVA_VERSION}" + PATH = "${env.JAVA_HOME}\\bin;${env.PATH}" + } + steps { + withGithubNotify(context: 'Build & Test Windows') { + deleteDir() + unstash 'source' + dir("${BASE_DIR}") { + echo "${env.PATH}" + retryWithSleep(retries: 5, seconds: 10) { + bat label: 'mvn clean install', script: "mvnw clean install -DskipTests=true -Dmaven.javadoc.skip=true -Dmaven.gitcommitid.skip=true" } + bat label: 'mvn test', script: "mvnw test" } } } @@ -177,13 +229,17 @@ pipeline { expression { return env.GITHUB_COMMENT?.contains('integration tests') } expression { matchesPrLabel(label: 'ci:agent-integration') } expression { return env.CHANGE_ID != null && !pullRequest.draft } + not { changeRequest() } } } + environment { + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } steps { withGithubNotify(context: 'Non-Application Server integration tests', tab: 'tests') { deleteDir() - unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") - dir("${BASE_DIR}"){ + unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") + dir("${BASE_DIR}") { withOtelEnv() { sh './mvnw -q -P ci-non-application-server-integration-tests verify' } @@ -206,13 +262,17 @@ pipeline { expression { return env.GITHUB_COMMENT?.contains('integration tests') } expression { matchesPrLabel(label: 'ci:agent-integration') } expression { return env.CHANGE_ID != null && !pullRequest.draft } + not { changeRequest() } } } + environment { + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } steps { withGithubNotify(context: 'Application Server integration tests', tab: 'tests') { deleteDir() - unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") - dir("${BASE_DIR}"){ + unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") + dir("${BASE_DIR}") { withOtelEnv() { sh './mvnw -q -P ci-application-server-integration-tests verify' } @@ -226,29 +286,28 @@ pipeline { } } /** - Run the benchmarks and store the results on ES. - The result JSON files are also archive into Jenkins. - */ + * Run the benchmarks and store the results on ES. + * The result JSON files are also archive into Jenkins. + */ stage('Benchmarks') { agent { label 'metal' } options { skipDefaultCheckout() } environment { NO_BUILD = "true" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" } when { beforeAgent true - allOf { - anyOf { - branch 'main' - expression { return env.GITHUB_COMMENT?.contains('benchmark tests') } - } + anyOf { + branch 'main' + expression { return env.GITHUB_COMMENT?.contains('benchmark tests') } expression { return params.bench_ci } } } steps { withGithubNotify(context: 'Benchmarks', tab: 'artifacts') { deleteDir() - unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") + unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") dir("${BASE_DIR}"){ withOtelEnv() { sh './scripts/jenkins/run-benchmarks.sh' @@ -266,14 +325,13 @@ pipeline { } } /** - Build javadoc files. - */ + * Build javadoc + */ stage('Javadoc') { agent { label 'linux && immutable' } options { skipDefaultCheckout() } - when { - beforeAgent true - expression { return env.ONLY_DOCS == "false" } + environment { + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" } steps { withGithubNotify(context: 'Javadoc') { @@ -302,6 +360,7 @@ pipeline { expression { return env.GITHUB_COMMENT?.contains('end-to-end tests') } expression { matchesPrLabel(label: 'ci:end-to-end') } expression { return env.CHANGE_ID != null && !pullRequest.draft } + not { changeRequest() } } } } @@ -328,25 +387,28 @@ pipeline { } } } + environment { + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } matrix { agent { label 'linux && immutable' } axes { axis { // the list of support java versions can be found in the infra repo (ansible/roles/java/defaults/main.yml) - name 'JAVA_VERSION' + name 'JDK_VERSION' // 'openjdk18' disabled for now see https://github.com/elastic/apm-agent-java/issues/2328 values 'openjdk17' } } stages { - stage('Test') { + stage('JDK Unit Tests') { steps { - withGithubNotify(context: "Unit Tests ${JAVA_VERSION}", tab: 'tests') { + withGithubNotify(context: "Unit Tests ${JDK_VERSION}", tab: 'tests') { deleteDir() - unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") + unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}") dir("${BASE_DIR}"){ withOtelEnv() { - sh(label: "./mvnw test for ${JAVA_VERSION}", script: './mvnw test') + sh(label: "./mvnw test for ${JDK_VERSION}", script: './mvnw test') } } } @@ -362,7 +424,7 @@ pipeline { } } } - stage('Releases'){ + stage('Releases') { when { anyOf { branch 'main' @@ -430,5 +492,7 @@ def reportTestResults(){ junit(allowEmptyResults: true, keepLongStdio: true, testResults: "${BASE_DIR}/**/junit-*.xml,${BASE_DIR}/**/TEST-*.xml") - codecov(repo: env.REPO, basedir: "${BASE_DIR}", secret: "${CODECOV_SECRET}") + + // disable codecov for now as it's not supported for windows + // codecov(repo: env.REPO, basedir: "${BASE_DIR}", secret: "${CODECOV_SECRET}") } diff --git a/apm-agent-api/pom.xml b/apm-agent-api/pom.xml index c48cd150fa..58e0e60aeb 100644 --- a/apm-agent-api/pom.xml +++ b/apm-agent-api/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent-api diff --git a/apm-agent-attach-cli/pom.xml b/apm-agent-attach-cli/pom.xml index dac4464bd0..de61597487 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-attach/pom.xml b/apm-agent-attach/pom.xml index 5a6a7254aa..882f786713 100644 --- a/apm-agent-attach/pom.xml +++ b/apm-agent-attach/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent-attach diff --git a/apm-agent-benchmarks/pom.xml b/apm-agent-benchmarks/pom.xml index 0020b4c04c..722c484829 100644 --- a/apm-agent-benchmarks/pom.xml +++ b/apm-agent-benchmarks/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent-benchmarks diff --git a/apm-agent-bootstrap/pom.xml b/apm-agent-bootstrap/pom.xml index 8579269a2d..94a130b166 100644 --- a/apm-agent-bootstrap/pom.xml +++ b/apm-agent-bootstrap/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent-bootstrap diff --git a/apm-agent-cached-lookup-key/pom.xml b/apm-agent-cached-lookup-key/pom.xml index af4c49cbe5..3d087265f3 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-common/pom.xml b/apm-agent-common/pom.xml index 65a07fc259..19c0ea89fc 100644 --- a/apm-agent-common/pom.xml +++ b/apm-agent-common/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml index b11ac7f80b..7cd5a1f435 100644 --- a/apm-agent-core/pom.xml +++ b/apm-agent-core/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent-core diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java index 7cd988b920..7950599e93 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java @@ -24,7 +24,6 @@ import co.elastic.apm.agent.bci.bytebuddy.InstallationListenerImpl; import co.elastic.apm.agent.bci.bytebuddy.Instrumented; import co.elastic.apm.agent.bci.bytebuddy.LruTypePoolCache; -import co.elastic.apm.agent.bci.bytebuddy.MatcherTimer; import co.elastic.apm.agent.bci.bytebuddy.MinimumClassFileVersionValidator; import co.elastic.apm.agent.bci.bytebuddy.NonInstrumented; import co.elastic.apm.agent.bci.bytebuddy.PatchBytecodeVersionTo51Transformer; @@ -38,6 +37,8 @@ import co.elastic.apm.agent.impl.GlobalTracer; import co.elastic.apm.agent.matcher.MethodMatcher; import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; import co.elastic.apm.agent.tracemethods.TraceMethodInstrumentation; @@ -61,8 +62,6 @@ import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.pool.TypePool; import net.bytebuddy.utility.JavaModule; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.source.ConfigurationSource; @@ -108,7 +107,8 @@ public class ElasticApmAgent { @Nullable private static Logger logger; - private static final ConcurrentMap matcherTimers = new ConcurrentHashMap<>(); + private static final InstrumentationStats instrumentationStats = new InstrumentationStats(); + @Nullable private static Instrumentation instrumentation; @Nullable @@ -258,10 +258,10 @@ private static synchronized void initInstrumentation(final ElasticApmTracer trac @Override public void run() { tracer.stop(); - matcherTimers.clear(); + instrumentationStats.reset(); } }); - matcherTimers.clear(); + instrumentationStats.reset(); Logger logger = getLogger(); if (ElasticApmAgent.instrumentation != null) { logger.warn("Instrumentation has already been initialized"); @@ -329,6 +329,7 @@ private static AgentBuilder initAgentBuilder(ElasticApmTracer tracer, Instrument int numberOfAdvices = 0; for (final ElasticApmInstrumentation advice : instrumentations) { if (isIncluded(advice, coreConfiguration)) { + instrumentationStats.addInstrumentation(advice); try { agentBuilder = applyAdvice(tracer, agentBuilder, advice, advice.getTypeMatcher()); numberOfAdvices++; @@ -396,7 +397,7 @@ public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, } return typeMatches; } finally { - getOrCreateTimer(instrumentation.getClass()).addTypeMatchingDuration(System.nanoTime() - start); + instrumentationStats.getOrCreateTimer(instrumentation.getClass()).addTypeMatchingDuration(System.nanoTime() - start); } } }) @@ -427,7 +428,11 @@ private static Logger getLogger() { } private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticApmInstrumentation instrumentation, final Logger logger, final ElementMatcher methodMatcher) { - validateAdvice(instrumentation); + boolean validate = false; + assert validate = true; + if (validate) { + validateAdvice(instrumentation); + } Advice.WithCustomMapping withCustomMapping = Advice .withCustomMapping() .with(new Advice.AssignReturned.Factory().withSuppressed(ClassCastException.class)) @@ -454,10 +459,11 @@ public boolean matches(MethodDescription target) { if (matches) { logger.debug("Method match for instrumentation {}: {} matches {}", instrumentation.getClass().getSimpleName(), methodMatcher, target); + instrumentationStats.addUsedInstrumentation(instrumentation); } return matches; } finally { - getOrCreateTimer(instrumentation.getClass()).addMethodMatchingDuration(System.nanoTime() - start); + instrumentationStats.getOrCreateTimer(instrumentation.getClass()).addMethodMatchingDuration(System.nanoTime() - start); } } }, instrumentation.getAdviceClassName()) @@ -560,27 +566,8 @@ private static void checkNotAgentType(TypeDescription.Generic type, String descr } } - private static MatcherTimer getOrCreateTimer(Class adviceClass) { - final String name = adviceClass.getName(); - MatcherTimer timer = matcherTimers.get(name); - if (timer == null) { - matcherTimers.putIfAbsent(name, new MatcherTimer(name)); - return matcherTimers.get(name); - } else { - return timer; - } - } - - static long getTotalMatcherTime() { - long totalTime = 0; - for (MatcherTimer value : matcherTimers.values()) { - totalTime += value.getTotalTime(); - } - return totalTime; - } - - static Collection getMatcherTimers() { - return matcherTimers.values(); + static InstrumentationStats getInstrumentationStats() { + return instrumentationStats; } // may help to debug classloading problems diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/InstrumentationStats.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/InstrumentationStats.java new file mode 100644 index 0000000000..19e77002db --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/InstrumentationStats.java @@ -0,0 +1,95 @@ +/* + * 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.bci; + +import co.elastic.apm.agent.bci.bytebuddy.MatcherTimer; +import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public final class InstrumentationStats { + + private final Set allInstrumentations = new HashSet<>(); + + private final ConcurrentMap usedInstrumentations = new ConcurrentHashMap<>(); + + private final ConcurrentMap matcherTimers = new ConcurrentHashMap<>(); + + void reset() { + allInstrumentations.clear(); + usedInstrumentations.clear(); + matcherTimers.clear(); + } + + void addInstrumentation(ElasticApmInstrumentation instrumentation) { + allInstrumentations.add(instrumentation); + } + + void addUsedInstrumentation(ElasticApmInstrumentation instrumentation) { + usedInstrumentations.put(instrumentation, Boolean.TRUE); + } + + Collection getUsedInstrumentationGroups() { + Set usedInstrumentationGroups = new TreeSet<>(); + for (ElasticApmInstrumentation instrumentation : usedInstrumentations.keySet()) { + usedInstrumentationGroups.addAll(instrumentation.getInstrumentationGroupNames()); + } + for (ElasticApmInstrumentation instrumentation : allInstrumentations) { + if (usedInstrumentations.containsKey(instrumentation)) { + continue; + } + Collection instrumentationGroups = instrumentation.getInstrumentationGroupNames(); + if (usedInstrumentationGroups.containsAll(instrumentationGroups)) { + continue; + } + usedInstrumentationGroups.removeAll(instrumentationGroups); + } + + return usedInstrumentationGroups; + } + + MatcherTimer getOrCreateTimer(Class adviceClass) { + final String name = adviceClass.getName(); + MatcherTimer timer = matcherTimers.get(name); + if (timer == null) { + matcherTimers.putIfAbsent(name, new MatcherTimer(name)); + return matcherTimers.get(name); + } else { + return timer; + } + } + + long getTotalMatcherTime() { + long totalTime = 0; + for (MatcherTimer value : matcherTimers.values()) { + totalTime += value.getTotalTime(); + } + return totalTime; + } + + Collection getMatcherTimers() { + return matcherTimers.values(); + } + +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/MatcherTimerLifecycleListener.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/InstrumentationStatsLifecycleListener.java similarity index 79% rename from apm-agent-core/src/main/java/co/elastic/apm/agent/bci/MatcherTimerLifecycleListener.java rename to apm-agent-core/src/main/java/co/elastic/apm/agent/bci/InstrumentationStatsLifecycleListener.java index 2a45f232fb..fb63525577 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/MatcherTimerLifecycleListener.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/InstrumentationStatsLifecycleListener.java @@ -26,16 +26,18 @@ import java.util.ArrayList; import java.util.Collections; -public class MatcherTimerLifecycleListener extends AbstractLifecycleListener { - private static final Logger logger = LoggerFactory.getLogger(MatcherTimerLifecycleListener.class); +public class InstrumentationStatsLifecycleListener extends AbstractLifecycleListener { + private static final Logger logger = LoggerFactory.getLogger(InstrumentationStatsLifecycleListener.class); @Override public void stop() { + InstrumentationStats instrumentationStats = ElasticApmAgent.getInstrumentationStats(); + logger.info("Used instrumentation groups: {}", instrumentationStats.getUsedInstrumentationGroups()); if (logger.isDebugEnabled()) { - final ArrayList matcherTimers = new ArrayList<>(ElasticApmAgent.getMatcherTimers()); + final ArrayList matcherTimers = new ArrayList<>(instrumentationStats.getMatcherTimers()); Collections.sort(matcherTimers); StringBuilder sb = new StringBuilder() - .append("Total time spent matching: ").append(String.format("%,d", ElasticApmAgent.getTotalMatcherTime())).append("ns") + .append("Total time spent matching: ").append(String.format("%,d", instrumentationStats.getTotalMatcherTime())).append("ns") .append('\n') .append(MatcherTimer.getTableHeader()) .append('\n'); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/IndyPluginClassLoader.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/IndyPluginClassLoader.java index 271bb3d07c..27494243ea 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/IndyPluginClassLoader.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/IndyPluginClassLoader.java @@ -68,7 +68,7 @@ agentClassLoader, startsWith("co.elastic.apm.agent").or(startsWith("net.bytebudd // The list of packages not to load should correspond with matching dependency exclusions from the apm-agent-core in apm-agent-plugins/pom.xml // As we're using a custom logging facade, plugins don't need to refer to the agent-bundled log4j2 or slf4j. return new DiscriminatingMultiParentClassLoader( - agentClassLoader, not(startsWith("org.apache.logging.log4j").and(not(startsWith("org.slf4j")))), + agentClassLoader, not(startsWith("org.apache.logging.log4j")).and(not(startsWith("org.slf4j"))).and(not(startsWith("co.elastic.logging.log4j2"))), targetClassLoader, ElementMatchers.any()); } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index d6679b1491..c1714f62d5 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -92,21 +92,61 @@ public class CoreConfiguration extends ConfigurationOptionProvider { .description("This is used to keep all the errors and transactions of your service together\n" + "and is the primary filter in the Elastic APM user interface.\n" + "\n" + + "Instead of configuring the service name manually,\n" + + "you can also choose to rely on the service name auto-detection mechanisms of the agent.\n" + + "If `service_name` is set explicitly, all auto-detection mechanisms are disabled.\n" + + "\n" + + "This is how the service name auto-detection works:\n" + + "\n" + + "* For standalone applications\n" + + "** The agent uses `Implementation-Title` in the `META-INF/MANIFEST.MF` file if the application is started via `java -jar`.\n" + + "** Falls back to the name of the main class or jar file.\n" + + "* For applications that are deployed to a servlet container/application server, the agent auto-detects the name for each application.\n" + + "** For Spring-based applications, the agent uses the `spring.application.name` property, if set.\n" + + "** For servlet-based applications, falls back to the `Implementation-Title` in the `META-INF/MANIFEST.MF` file.\n" + + "** Falls back to the `display-name` of the `web.xml`, if available.\n" + + "** Falls back to the servlet context path the application is mapped to (unless mapped to the root context).\n" + + "\n" + + "Generally, it is recommended to rely on the service name detection based on `META-INF/MANIFEST.MF`.\n" + + "Spring Boot automatically adds the relevant manifest entries.\n" + + "For other applications that are built with Maven, this is how you add the manifest entries:\n" + + "\n" + + "<#noparse>\n" + + "[source,xml]\n" + + ".pom.xml\n" + + "----\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " maven-jar-plugin\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " true\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "----\n" + + "\n" + + "\n" + "The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]+$`.\n" + "In less regexy terms:\n" + "Your service name must only contain characters from the ASCII alphabet, numbers, dashes, underscores and spaces.\n" + "\n" + - "NOTE: When relying on auto-discovery of the service name in Servlet environments (including Spring Boot),\n" + - "there is currently a caveat related to metrics.\n" + - "The consequence is that the 'Metrics' tab of a service does not show process-global metrics like CPU utilization.\n" + - "The reason is that metrics are reported with the detected default service name for the JVM,\n" + - "for example `tomcat-application`.\n" + - "That is because there may be multiple web applications deployed to a single JVM/servlet container.\n" + - "However, you can view those metrics by selecting the `tomcat-application` service name, for example.\n" + - "Future versions of the Elastic APM stack will have better support for that scenario.\n" + - "A workaround is to explicitly set the `service_name` which means all applications deployed to the same servlet container will have the same name\n" + - "or to disable the corresponding `*-service-name` detecting instrumentations via <>.\n" + - "\n" + "NOTE: Service name auto discovery mechanisms require APM Server 7.0+.") .addValidator(RegexValidator.of("^[a-zA-Z0-9 _-]+$", "Your service name \"{0}\" must only contain characters " + "from the ASCII alphabet, numbers, dashes, underscores and spaces")) @@ -144,7 +184,12 @@ public class CoreConfiguration extends ConfigurationOptionProvider { .configurationCategory(CORE_CATEGORY) .description("A version string for the currently deployed version of the service. If you don’t version your deployments, " + "the recommended value for this field is the commit identifier of the deployed revision, " + - "e.g. the output of git rev-parse HEAD.") + "e.g. the output of git rev-parse HEAD.\n" + + "\n" + + "Similar to the auto-detection of <>, " + + "the agent can auto-detect the service version based on the `Implementation-Title` attribute in `META-INF/MANIFEST.MF`.\n" + + "See <> on how to set this attribute.\n" + + "\n") .defaultValue(ServiceInfo.autoDetected().getServiceVersion()) .build(); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java index fafb9f41ad..f83398fa22 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java @@ -19,25 +19,34 @@ package co.elastic.apm.agent.configuration; import javax.annotation.Nullable; +import java.util.Objects; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.JarFile; +import java.util.jar.Manifest; public class ServiceInfo { private static final String JAR_VERSION_SUFFIX = "-(\\d+\\.)+(\\d+)(.*)?$"; private static final String DEFAULT_SERVICE_NAME = "unknown-java-service"; + private static final ServiceInfo EMPTY = new ServiceInfo(null, null); private static final ServiceInfo AUTO_DETECTED = autoDetect(System.getProperties()); private final String serviceName; @Nullable private final String serviceVersion; + private final boolean multiServiceContainer; public ServiceInfo(@Nullable String serviceName) { this(serviceName, null); } - public ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) { + private ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) { + this(serviceName, serviceVersion, false); + } + + private ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion, boolean multiServiceContainer) { + this.multiServiceContainer = multiServiceContainer; if (serviceName == null || serviceName.trim().isEmpty()) { this.serviceName = DEFAULT_SERVICE_NAME; } else { @@ -46,16 +55,27 @@ public ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion this.serviceVersion = serviceVersion; } - public String getServiceName() { - return serviceName; + public static ServiceInfo empty() { + return EMPTY; } - @Nullable - public String getServiceVersion() { - return serviceVersion; + public static ServiceInfo of(@Nullable String serviceName) { + return of(serviceName, null); + } + + public static ServiceInfo ofMultiServiceContainer(String serviceName) { + return new ServiceInfo(serviceName, null, true); } - public static String replaceDisallowedServiceNameChars(String serviceName) { + public static ServiceInfo of(@Nullable String serviceName, @Nullable String serviceVersion) { + if ((serviceName == null || serviceName.isEmpty()) && + (serviceVersion == null || serviceVersion.isEmpty())) { + return empty(); + } + return new ServiceInfo(serviceName, serviceVersion); + } + + private static String replaceDisallowedServiceNameChars(String serviceName) { return serviceName.replaceAll("[^a-zA-Z0-9 _-]", "-"); } @@ -72,7 +92,7 @@ public static ServiceInfo autoDetect(Properties properties) { if (serviceInfo != null) { return serviceInfo; } - return new ServiceInfo(null); + return ServiceInfo.empty(); } } @@ -84,12 +104,12 @@ private static ServiceInfo createFromSunJavaCommand(@Nullable String command) { command = command.trim(); String serviceName = getContainerServiceName(command); if (serviceName != null) { - return new ServiceInfo(serviceName); + return ServiceInfo.ofMultiServiceContainer(serviceName); } if (command.contains(".jar")) { - return parseJarCommand(command); + return fromJarCommand(command); } else { - return parseMainClass(command); + return fromMainClassCommand(command); } } @@ -111,26 +131,32 @@ private static String getContainerServiceName(String command) { return null; } - private static ServiceInfo parseJarCommand(String command) { + private static ServiceInfo fromJarCommand(String command) { final String[] commandParts = command.split(" "); - String serviceName = null; - String serviceVersion = null; + ServiceInfo serviceInfoFromManifest = ServiceInfo.empty(); + ServiceInfo serviceInfoFromJarName = ServiceInfo.empty(); for (String commandPart : commandParts) { if (commandPart.endsWith(".jar")) { try (JarFile jarFile = new JarFile(commandPart)) { - Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); - serviceName = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE); - serviceVersion = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + serviceInfoFromManifest = fromManifest(jarFile.getManifest()); } catch (Exception ignored) { } - if (serviceName == null || serviceName.isEmpty()) { - serviceName = removeVersionFromJar(removePath(removeJarExtension(commandPart))); - } + serviceInfoFromJarName = ServiceInfo.of(removeVersionFromJar(removePath(removeJarExtension(commandPart)))); break; } } - return new ServiceInfo(serviceName, serviceVersion); + return serviceInfoFromManifest.withFallback(serviceInfoFromJarName); + } + + public static ServiceInfo fromManifest(@Nullable Manifest manifest) { + if (manifest == null) { + return ServiceInfo.empty(); + } + Attributes mainAttributes = manifest.getMainAttributes(); + return ServiceInfo.of( + mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE), + mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION)); } private static String removeJarExtension(String commandPart) { @@ -145,7 +171,7 @@ private static String removeVersionFromJar(String jarFileName) { return jarFileName.replaceFirst(JAR_VERSION_SUFFIX, ""); } - private static ServiceInfo parseMainClass(String command) { + private static ServiceInfo fromMainClassCommand(String command) { final String mainClassName; int indexOfSpace = command.indexOf(' '); if (indexOfSpace != -1) { @@ -155,4 +181,58 @@ private static ServiceInfo parseMainClass(String command) { } return new ServiceInfo(mainClassName.substring(mainClassName.lastIndexOf('.') + 1)); } + + public String getServiceName() { + return serviceName; + } + + @Nullable + public String getServiceVersion() { + return serviceVersion; + } + + /** + * Returns true if the service is a container service that can host multiple other applications. + * For example, an application server or servlet container. + * A standalone application that's built on embedded Tomcat, for example, would return {@code false}. + */ + public boolean isMultiServiceContainer() { + return multiServiceContainer; + } + + public ServiceInfo withFallback(ServiceInfo fallback) { + return ServiceInfo.of( + hasServiceName() ? serviceName : fallback.serviceName, + serviceVersion != null ? serviceVersion : fallback.serviceVersion); + } + + public boolean hasServiceName() { + return !serviceName.equals(DEFAULT_SERVICE_NAME); + } + + public boolean isEmpty() { + return !hasServiceName() && serviceVersion == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceInfo that = (ServiceInfo) o; + return multiServiceContainer == that.multiServiceContainer && serviceName.equals(that.serviceName) && Objects.equals(serviceVersion, that.serviceVersion); + } + + @Override + public int hashCode() { + return Objects.hash(serviceName, serviceVersion, multiServiceContainer); + } + + @Override + public String toString() { + return "ServiceInfo{" + + "serviceName='" + serviceName + '\'' + + ", serviceVersion='" + serviceVersion + '\'' + + ", multiServiceContainer=" + multiServiceContainer + + '}'; + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index 4e129f63a3..e7da0791ea 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -34,6 +34,7 @@ import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.logging.LoggingConfiguration; import co.elastic.apm.agent.matcher.WildcardMatcher; import co.elastic.apm.agent.metrics.MetricRegistry; import co.elastic.apm.agent.objectpool.ObjectPool; @@ -41,12 +42,12 @@ import co.elastic.apm.agent.report.ApmServerClient; import co.elastic.apm.agent.report.Reporter; import co.elastic.apm.agent.report.ReporterConfiguration; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; import co.elastic.apm.agent.util.DependencyInjectingServiceLoader; import co.elastic.apm.agent.util.ExecutorUtils; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.ConfigurationOptionProvider; import org.stagemonitor.configuration.ConfigurationRegistry; @@ -72,7 +73,7 @@ public class ElasticApmTracer implements Tracer { private static final Logger logger = LoggerFactory.getLogger(ElasticApmTracer.class); - private static final WeakMap serviceNameByClassLoader = WeakConcurrent.buildMap(); + private static final WeakMap serviceInfoByClassLoader = WeakConcurrent.buildMap(); private final ConfigurationRegistry configurationRegistry; private final StacktraceConfiguration stacktraceConfiguration; @@ -230,9 +231,10 @@ private void afterTransactionStart(@Nullable ClassLoader initiatingClassLoader, new RuntimeException("this exception is just used to record where the transaction has been started from")); } } - final String serviceName = getServiceName(initiatingClassLoader); - if (serviceName != null) { - transaction.getTraceContext().setServiceName(serviceName); + final ServiceInfo serviceInfo = getServiceInfo(initiatingClassLoader); + if (serviceInfo != null) { + transaction.getTraceContext().setServiceName(serviceInfo.getServiceName()); + transaction.getTraceContext().setServiceVersion(serviceInfo.getServiceVersion()); } } @@ -342,7 +344,11 @@ private ErrorCapture captureException(long epochMicros, @Nullable Throwable e, @ parent.setNonDiscardable(); } else { error.getTraceContext().getId().setToRandomValue(); - error.getTraceContext().setServiceName(getServiceName(initiatingClassLoader)); + ServiceInfo serviceInfo = getServiceInfo(initiatingClassLoader); + if (serviceInfo != null) { + error.getTraceContext().setServiceName(serviceInfo.getServiceName()); + error.getTraceContext().setServiceVersion(serviceInfo.getServiceVersion()); + } } return error; } @@ -457,6 +463,7 @@ public synchronized void stop() { } catch (Exception e) { logger.warn("Suppressed exception while calling stop()", e); } + LoggingConfiguration.shutdown(); } public Reporter getReporter() { @@ -734,42 +741,41 @@ public MetricRegistry getMetricRegistry() { return metricRegistry; } - public List getServiceNameOverrides() { - List serviceNames = new ArrayList<>(serviceNameByClassLoader.approximateSize()); - for (Map.Entry entry : serviceNameByClassLoader) { - serviceNames.add(entry.getValue()); + public List getServiceInfoOverrides() { + List serviceInfos = new ArrayList<>(serviceInfoByClassLoader.approximateSize()); + for (Map.Entry entry : serviceInfoByClassLoader) { + serviceInfos.add(entry.getValue()); } - return serviceNames; + return serviceInfos; } @Override - public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) { - // overriding the service name for the bootstrap class loader is not an actual use-case + public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { + // overriding the service name/version for the bootstrap class loader is not an actual use-case // null may also mean we don't know about the initiating class loader if (classLoader == null - || serviceName == null || serviceName.isEmpty() + || !serviceInfo.hasServiceName() // if the service name is set explicitly, don't override it || coreConfiguration.getServiceNameConfig().getUsedKey() != null) { return; } - String sanitizedServiceName = ServiceInfo.replaceDisallowedServiceNameChars(serviceName); - logger.debug("Using `{}` as the service name for class loader [{}]", sanitizedServiceName, classLoader); - if (!serviceNameByClassLoader.containsKey(classLoader)) { - serviceNameByClassLoader.putIfAbsent(classLoader, sanitizedServiceName); + logger.debug("Using `{}` as the service name and `{}` as the service version for class loader [{}]", serviceInfo.getServiceName(), serviceInfo.getServiceVersion(), classLoader); + if (!serviceInfoByClassLoader.containsKey(classLoader)) { + serviceInfoByClassLoader.putIfAbsent(classLoader, serviceInfo); } } @Nullable - private String getServiceName(@Nullable ClassLoader initiatingClassLoader) { + public ServiceInfo getServiceInfo(@Nullable ClassLoader initiatingClassLoader) { if (initiatingClassLoader == null) { return null; } - return serviceNameByClassLoader.get(initiatingClassLoader); + return serviceInfoByClassLoader.get(initiatingClassLoader); } - public void resetServiceNameOverrides() { - serviceNameByClassLoader.clear(); + public void resetServiceInfoOverrides() { + serviceInfoByClassLoader.clear(); } public ApmServerClient getApmServerClient() { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/GlobalTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/GlobalTracer.java index a705cc6c74..c7b72ea4fc 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/GlobalTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/GlobalTracer.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.impl; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.sampling.Sampler; import co.elastic.apm.agent.impl.transaction.AbstractSpan; @@ -204,8 +205,8 @@ public TracerState getState() { } @Override - public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) { - tracer.overrideServiceNameForClassLoader(classLoader, serviceName); + public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { + tracer.overrideServiceInfoForClassLoader(classLoader, serviceInfo); } @Override diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/NoopTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/NoopTracer.java index cfbd816217..4621a45b18 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/NoopTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/NoopTracer.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.impl; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.sampling.Sampler; import co.elastic.apm.agent.impl.transaction.AbstractSpan; @@ -130,7 +131,7 @@ public TracerState getState() { } @Override - public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) { + public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { } @Override diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java index f17970d64f..ade46e7c7a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.impl; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.sampling.Sampler; import co.elastic.apm.agent.impl.transaction.AbstractSpan; @@ -153,16 +154,16 @@ Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet TracerState getState(); /** - * Overrides the service name for all {@link Transaction}s, + * Overrides the service name and version for all {@link Transaction}s, * {@link Span}s and {@link ErrorCapture}s which are created by the service which corresponds to the provided {@link ClassLoader}. *

* The main use case is being able to differentiate between multiple services deployed to the same application server. *

* * @param classLoader the class loader which corresponds to a particular service - * @param serviceName the service name for this class loader + * @param serviceInfo the service name and version for this class loader */ - void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName); + void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo); /** * Called when the container shuts down. diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index 4c71bf330c..455e6272b6 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -241,6 +241,9 @@ public static void copyTraceContextTextHeaders(S source, TextHeaderGetter @Nullable private String serviceName; + @Nullable + private String serviceVersion; + private TraceContext(ElasticApmTracer tracer, Id id) { coreConfiguration = tracer.getConfig(CoreConfiguration.class); traceState = new TraceState(); @@ -436,6 +439,7 @@ public void asChildOf(TraceContext parent) { id.setToRandomValue(); clock.init(parent.clock); serviceName = parent.serviceName; + serviceVersion = parent.serviceVersion; applicationClassLoader = parent.applicationClassLoader; traceState.copyFrom(parent.traceState); onMutation(); @@ -452,6 +456,7 @@ public void resetState() { discardable = true; clock.resetState(); serviceName = null; + serviceVersion = null; applicationClassLoader = null; traceState.resetState(); traceState.setSizeLimit(coreConfiguration.getTracestateSizeLimit()); @@ -652,6 +657,7 @@ public void copyFrom(TraceContext other) { discardable = other.discardable; clock.init(other.clock); serviceName = other.serviceName; + serviceVersion = other.serviceVersion; applicationClassLoader = other.applicationClassLoader; traceState.copyFrom(other.traceState); onMutation(); @@ -684,6 +690,20 @@ public void setServiceName(@Nullable String serviceName) { this.serviceName = serviceName; } + @Nullable + public String getServiceVersion() { + return serviceVersion; + } + + /** + * Overrides the {@code co.elastic.apm.agent.impl.payload.Service#version} property sent via the meta data Intake V2 event. + * + * @param serviceVersion the service version for this event + */ + public void setServiceVersion(@Nullable String serviceVersion) { + this.serviceVersion = serviceVersion; + } + public Span createSpan() { return tracer.startSpan(fromParentContext(), this); } @@ -753,7 +773,7 @@ public void serialize(byte[] buffer) { ByteUtils.putLong(buffer, offset, clock.getOffset()); } - private void asChildOf(byte[] buffer, @Nullable String serviceName) { + private void asChildOf(byte[] buffer, @Nullable String serviceName, @Nullable String serviceVersion) { int offset = 0; offset += traceId.fromBytes(buffer, offset); offset += parentId.fromBytes(buffer, offset); @@ -763,10 +783,11 @@ private void asChildOf(byte[] buffer, @Nullable String serviceName) { discardable = buffer[offset++] == (byte) 1; clock.init(ByteUtils.getLong(buffer, offset)); this.serviceName = serviceName; + this.serviceVersion = serviceVersion; onMutation(); } - public void deserialize(byte[] buffer, @Nullable String serviceName) { + public void deserialize(byte[] buffer, @Nullable String serviceName, @Nullable String serviceVersion) { int offset = 0; offset += traceId.fromBytes(buffer, offset); offset += id.fromBytes(buffer, offset); @@ -775,6 +796,7 @@ public void deserialize(byte[] buffer, @Nullable String serviceName) { discardable = buffer[offset++] == (byte) 1; clock.init(ByteUtils.getLong(buffer, offset)); this.serviceName = serviceName; + this.serviceVersion = serviceVersion; onMutation(); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java index 71d563a730..00172f9031 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java @@ -420,7 +420,10 @@ private void trackMetrics() { } final Labels.Mutable labels = labelsThreadLocal.get(); labels.resetState(); - labels.serviceName(getTraceContext().getServiceName()).transactionName(name).transactionType(type); + labels.serviceName(getTraceContext().getServiceName()) + .serviceVersion(getTraceContext().getServiceVersion()) + .transactionName(name) + .transactionType(type); final MetricRegistry metricRegistry = tracer.getMetricRegistry(); long criticalValueAtEnter = metricRegistry.writerCriticalSectionEnter(); try { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java index 8f5d91ca5c..a253ae1f85 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java @@ -121,7 +121,8 @@ public Configuration getConfiguration(LoggerContext loggerContext, Configuration Configuration getConfiguration() { ConfigurationBuilder builder = newConfigurationBuilder(); builder.setStatusLevel(Level.ERROR) - .setConfigurationName("ElasticAPM"); + .setConfigurationName("ElasticAPM") + .setShutdownHook("disable"); Level level = getLogLevel(); RootLoggerComponentBuilder rootLogger = builder.newRootLogger(level); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4jLoggerFactoryBridge.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4jLoggerFactoryBridge.java index 41165e9d9c..fe338a33a0 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4jLoggerFactoryBridge.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4jLoggerFactoryBridge.java @@ -21,8 +21,10 @@ import co.elastic.apm.agent.sdk.logging.ILoggerFactory; import co.elastic.apm.agent.sdk.logging.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; import org.apache.logging.log4j.spi.AbstractLoggerAdapter; import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.util.StackLocatorUtil; /** @@ -33,6 +35,16 @@ public class Log4jLoggerFactoryBridge extends AbstractLoggerAdapter impl private static final String FQCN = Log4jLoggerFactoryBridge.class.getName(); private static final String PACKAGE = "co.elastic.apm.agent.sdk.logging"; + public static void shutdown() { + LoggerContextFactory factory = LogManager.getFactory(); + // the Spring tests use log4j-to-slf4j which uses SLF4JLoggerContextFactory which does not need cleanup + if (factory instanceof Log4jContextFactory) { + for (LoggerContext context : ((Log4jContextFactory) factory).getSelector().getLoggerContexts()) { + LogManager.shutdown(context); + } + } + } + @Override protected Logger newLogger(final String name, final LoggerContext context) { final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name; diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/LoggingConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/LoggingConfiguration.java index 2d57e636e6..0138ab1a8d 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/LoggingConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/LoggingConfiguration.java @@ -335,6 +335,10 @@ public static void init(List sources, String ephemeralId) { } } + public static void shutdown() { + Log4jLoggerFactoryBridge.shutdown(); + } + private static void restoreSystemProperty(String key, @Nullable String originalValue) { if (originalValue != null) { System.setProperty(key, originalValue); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java index bc7ebb1e1c..aeba09da07 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java @@ -48,6 +48,9 @@ public interface Labels { @Nullable String getServiceName(); + @Nullable + String getServiceVersion(); + @Nullable CharSequence getTransactionName(); @@ -92,7 +95,7 @@ public List getValues() { } public boolean isEmpty() { - return keys.isEmpty() && getServiceName() == null && getTransactionName() == null && getTransactionType() == null && getSpanType() == null; + return keys.isEmpty() && getServiceName() == null && getServiceVersion() == null && getTransactionName() == null && getTransactionType() == null && getSpanType() == null; } public int size() { @@ -117,6 +120,7 @@ public boolean equals(Object o) { Objects.equals(getTransactionType(), labels.getTransactionType()) && contentEquals(getTransactionName(), labels.getTransactionName()) && Objects.equals(getServiceName(), labels.getServiceName()) && + Objects.equals(getServiceVersion(), labels.getServiceVersion()) && keys.equals(labels.keys) && isEqual(values, labels.values); } @@ -128,6 +132,7 @@ public int hashCode() { h = 31 * h + hashEntryAt(i); } h = 31 * h + hash(getServiceName()); + h = 31 * h + hash(getServiceVersion()); h = 31 * h + hash(getTransactionName()); h = 31 * h + (getTransactionType() != null ? getTransactionType().hashCode() : 0); h = 31 * h + (getSpanType() != null ? getSpanType().hashCode() : 0); @@ -205,6 +210,8 @@ class Mutable extends AbstractBase implements Recyclable { @Nullable private String serviceName; @Nullable + private String serviceVersion; + @Nullable private CharSequence transactionName; @Nullable private String transactionType; @@ -246,6 +253,11 @@ public Labels.Mutable serviceName(@Nullable String serviceName) { return this; } + public Labels.Mutable serviceVersion(@Nullable String serviceVersion) { + this.serviceVersion = serviceVersion; + return this; + } + public Labels.Mutable transactionName(@Nullable CharSequence transactionName) { this.transactionName = transactionName; return this; @@ -271,6 +283,11 @@ public String getServiceName() { return serviceName; } + @Nullable + public String getServiceVersion() { + return serviceVersion; + } + @Nullable public CharSequence getTransactionName() { return transactionName; @@ -301,6 +318,7 @@ public void resetState() { keys.clear(); values.clear(); serviceName = null; + serviceVersion = null; transactionName = null; transactionType = null; spanType = null; @@ -323,6 +341,8 @@ class Immutable extends AbstractBase { @Nullable private final String serviceName; @Nullable + private final String serviceVersion; + @Nullable private final String transactionName; @Nullable private final String transactionType; @@ -334,6 +354,7 @@ class Immutable extends AbstractBase { public Immutable(Labels labels) { super(new ArrayList<>(labels.getKeys()), copy(labels.getValues())); this.serviceName = labels.getServiceName(); + this.serviceVersion = labels.getServiceVersion(); final CharSequence transactionName = labels.getTransactionName(); this.transactionName = transactionName != null ? transactionName.toString() : null; this.transactionType = labels.getTransactionType(); @@ -365,6 +386,12 @@ public String getServiceName() { return serviceName; } + @Nullable + @Override + public String getServiceVersion() { + return serviceVersion; + } + @Nullable @Override public String getTransactionName() { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/AbstractIntakeApiHandler.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/AbstractIntakeApiHandler.java index 49a6b2070a..86ad5e0dcb 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/AbstractIntakeApiHandler.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/AbstractIntakeApiHandler.java @@ -122,8 +122,8 @@ protected HttpURLConnection startRequest(String endpoint) throws Exception { payloadSerializer.flushToOutputStream(); requestStartedNanos = System.nanoTime(); } catch (IOException e) { - logger.error("Error trying to connect to APM Server at {}. Some details about SSL configurations corresponding " + - "the current connection are logged at INFO level.", connection.getURL()); + logger.error("Error trying to connect to APM Server at {}. Although not necessarily related to SSL, some related SSL " + + "configurations corresponding the current connection are logged at INFO level.", connection.getURL()); if (logger.isInfoEnabled() && connection instanceof HttpsURLConnection) { HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection; try { 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 deea291f50..e6ef5f1c9c 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 @@ -83,7 +83,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import static co.elastic.apm.agent.util.ObjectUtils.defaultIfNull; import static com.dslplatform.json.JsonWriter.ARRAY_END; import static com.dslplatform.json.JsonWriter.ARRAY_START; import static com.dslplatform.json.JsonWriter.COMMA; @@ -452,10 +451,11 @@ private static void serializeService(final Service service, final StringBuilder jw.writeByte(JsonWriter.OBJECT_END); } - private static void serializeServiceName(final CharSequence serviceName, final StringBuilder replaceBuilder, final JsonWriter jw) { + private static void serializeServiceNameAndVersion(final CharSequence serviceName, final CharSequence serviceVersion, final StringBuilder replaceBuilder, final JsonWriter jw) { if (serviceName != null) { writeFieldName("service", jw); jw.writeByte(OBJECT_START); + writeField("version", serviceVersion, replaceBuilder, jw); writeLastField("name", serviceName, replaceBuilder, jw); jw.writeByte(OBJECT_END); jw.writeByte(COMMA); @@ -714,8 +714,9 @@ private void serializeSpan(final Span span) { private void serializeServiceNameWithFramework(@Nullable final Transaction transaction, final TraceContext traceContext, final ServiceOrigin serviceOrigin) { String serviceName = traceContext.getServiceName(); + String serviceVersion = traceContext.getServiceVersion(); boolean isFrameworkNameNotNull = transaction != null && transaction.getFrameworkName() != null; - if (serviceName != null || isFrameworkNameNotNull || serviceOrigin.hasContent()) { + if (serviceName != null || serviceVersion != null || isFrameworkNameNotNull || serviceOrigin.hasContent()) { writeFieldName("service"); jw.writeByte(OBJECT_START); if (serviceOrigin.hasContent()) { @@ -724,7 +725,8 @@ private void serializeServiceNameWithFramework(@Nullable final Transaction trans if (isFrameworkNameNotNull) { serializeFramework(transaction.getFrameworkName(), transaction.getFrameworkVersion()); } - writeLastField("name", serviceName); + writeField("name", serviceName); + writeLastField("version", serviceVersion); jw.writeByte(OBJECT_END); jw.writeByte(COMMA); } @@ -924,7 +926,7 @@ private void serializeSpanContext(SpanContext context, TraceContext traceContext writeFieldName("context"); jw.writeByte(OBJECT_START); - serializeServiceName(traceContext.getServiceName(), replaceBuilder, jw); + serializeServiceNameAndVersion(traceContext.getServiceName(), traceContext.getServiceVersion(), replaceBuilder, jw); serializeMessageContext(context.getMessage()); serializeDbContext(context.getDb()); serializeHttpContext(context.getHttp()); @@ -1156,8 +1158,12 @@ private static void serializeStringKeyScalarValueMap(Iterator metricSets) { if (tracer.isRunning()) { - List serviceNames = tracer.getServiceNameOverrides(); + List serviceInfos = tracer.getServiceInfoOverrides(); for (MetricSet metricSet : metricSets.values()) { - JsonWriter jw = serializer.serialize(metricSet, serviceNames); + JsonWriter jw = serializer.serialize(metricSet, serviceInfos); if (jw != null) { reporter.report(jw); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java index e33650b51f..cc5ddf7349 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.report.serialize; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.metrics.DoubleSupplier; import co.elastic.apm.agent.metrics.MetricSet; import co.elastic.apm.agent.metrics.Timer; @@ -48,16 +49,18 @@ public class MetricRegistrySerializer { * @return the serialized metric-set or {@code null} if no samples were serialized */ @Nullable - public JsonWriter serialize(MetricSet metricSet, List serviceNames) { + public JsonWriter serialize(MetricSet metricSet, List serviceInfos) { JsonWriter jw = dslJson.newWriter(maxSerializedSize); boolean hasSamples = false; - if (serviceNames.isEmpty() || metricSet.getLabels().getServiceName() != null) { - hasSamples = serialize(metricSet, null, jw); + if (serviceInfos.isEmpty() || metricSet.getLabels().getServiceName() != null) { + hasSamples = serialize(metricSet, null, null, jw); } else { - hasSamples = serialize(metricSet, serviceNames.get(0), jw); + ServiceInfo serviceInfo = serviceInfos.get(0); + hasSamples = serialize(metricSet, serviceInfo.getServiceName(), serviceInfo.getServiceVersion(), jw); if (hasSamples) { - for (int i = 1; i < serviceNames.size(); ++i) { - serialize(metricSet, serviceNames.get(i), jw); + for (int i = 1; i < serviceInfos.size(); ++i) { + serviceInfo = serviceInfos.get(i); + serialize(metricSet, serviceInfo.getServiceName(), serviceInfo.getServiceVersion(), jw); } } } @@ -68,12 +71,12 @@ public JsonWriter serialize(MetricSet metricSet, List serviceNames) { return null; } - private boolean serialize(MetricSet metricSet, String serviceName, JsonWriter jw) { + private boolean serialize(MetricSet metricSet, String serviceName, String serviceVersion, JsonWriter jw) { final long timestamp = System.currentTimeMillis() * 1000; - return serialize(metricSet, timestamp, serviceName, replaceBuilder, jw); + return serialize(metricSet, timestamp, serviceName, serviceVersion, replaceBuilder, jw); } - private static boolean serialize(MetricSet metricSet, long epochMicros, String serviceName, StringBuilder replaceBuilder, JsonWriter jw) { + private static boolean serialize(MetricSet metricSet, long epochMicros, String serviceName, String serviceVersion, StringBuilder replaceBuilder, JsonWriter jw) { boolean hasSamples; jw.writeByte(JsonWriter.OBJECT_START); { @@ -83,7 +86,7 @@ private static boolean serialize(MetricSet metricSet, long epochMicros, String s DslJsonSerializer.writeFieldName("timestamp", jw); NumberConverter.serialize(epochMicros, jw); jw.writeByte(JsonWriter.COMMA); - DslJsonSerializer.serializeLabels(metricSet.getLabels(), serviceName, replaceBuilder, jw); + DslJsonSerializer.serializeLabels(metricSet.getLabels(), serviceName, serviceVersion, replaceBuilder, jw); DslJsonSerializer.writeFieldName("samples", jw); jw.writeByte(JsonWriter.OBJECT_START); hasSamples = serializeGauges(metricSet.getGauges(), jw); diff --git a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener index b5b6573c1c..94179a6da0 100644 --- a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener +++ b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener @@ -1,5 +1,5 @@ co.elastic.apm.agent.configuration.StartupInfo -co.elastic.apm.agent.bci.MatcherTimerLifecycleListener +co.elastic.apm.agent.bci.InstrumentationStatsLifecycleListener co.elastic.apm.agent.metrics.builtin.JvmMemoryMetrics co.elastic.apm.agent.metrics.builtin.SystemMetrics co.elastic.apm.agent.metrics.builtin.CGroupMetrics diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java index df3379c03b..c01def00c0 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java @@ -118,7 +118,7 @@ public final void cleanUp() { TracerInternalApiUtils.resumeTracer(tracer); } } - tracer.resetServiceNameOverrides(); + tracer.resetServiceInfoOverrides(); // reset reporter to default behaviour on all checks reporter.resetChecks(); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationStatsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationStatsTest.java new file mode 100644 index 0000000000..00706c5a7f --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationStatsTest.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.bci; + +import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class InstrumentationStatsTest { + + private static class NoopInstrumentation extends ElasticApmInstrumentation { + private final Collection instrumentationGroups; + + public NoopInstrumentation(Collection instrumentationGroups) { + this.instrumentationGroups = instrumentationGroups; + } + + @Override + public ElementMatcher getTypeMatcher() { + return null; + } + + @Override + public ElementMatcher getMethodMatcher() { + return null; + } + + @Override + public Collection getInstrumentationGroupNames() { + return instrumentationGroups; + } + } + + private final InstrumentationStats instrumentationStats = new InstrumentationStats(); + + @Test + void testOnlyUnusedInstrumentations() { + instrumentationStats.addInstrumentation(new NoopInstrumentation(Set.of("a", "b"))); + + assertThat(instrumentationStats.getUsedInstrumentationGroups()).isEmpty(); + } + + @Test + void testOnlyUsedInstrumentations() { + NoopInstrumentation instrumentation = new NoopInstrumentation(Set.of("a", "b")); + + instrumentationStats.addInstrumentation(instrumentation); + instrumentationStats.addUsedInstrumentation(instrumentation); + + assertThat(instrumentationStats.getUsedInstrumentationGroups()).hasSameElementsAs(List.of("a", "b")); + } + + @Test + void testUsedAndUnusedInstrumentationsWithCommonGroups() { + NoopInstrumentation instrumentation1 = new NoopInstrumentation(Set.of("a", "b")); + NoopInstrumentation instrumentation2 = new NoopInstrumentation(Set.of("a", "c")); + + instrumentationStats.addInstrumentation(instrumentation1); + instrumentationStats.addInstrumentation(instrumentation2); + instrumentationStats.addUsedInstrumentation(instrumentation1); + + assertThat(instrumentationStats.getUsedInstrumentationGroups()).hasSameElementsAs(List.of("b")); + } + + @Test + void testUsedAndUnusedInstrumentationsWithSameGroups() { + NoopInstrumentation instrumentation1 = new NoopInstrumentation(Set.of("a", "b")); + NoopInstrumentation instrumentation2 = new NoopInstrumentation(Set.of("c", "d")); + NoopInstrumentation instrumentation3 = new NoopInstrumentation(Set.of("a", "b")); + + instrumentationStats.addInstrumentation(instrumentation1); + instrumentationStats.addInstrumentation(instrumentation2); + instrumentationStats.addInstrumentation(instrumentation3); + instrumentationStats.addUsedInstrumentation(instrumentation1); + + assertThat(instrumentationStats.getUsedInstrumentationGroups()).hasSameElementsAs(List.of("a", "b")); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java index 6a91096857..97418ce40c 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java @@ -20,13 +20,18 @@ import org.junit.jupiter.api.Test; +import javax.annotation.Nullable; +import java.util.Map; import java.util.Properties; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; class ServiceInfoTest { - private static String getDefaultServiceName(String sunJavaCommand) { + private static String getDefaultServiceName(@Nullable String sunJavaCommand) { Properties properties = new Properties(); if (sunJavaCommand != null) { properties.setProperty("sun.java.command", sunJavaCommand); @@ -88,4 +93,97 @@ void parseApplicationServers() { }); } + @Test + void testNormalizedName() { + checkServiceInfoEmpty(ServiceInfo.of("")); + checkServiceInfoEmpty(ServiceInfo.of(" ")); + + assertThat(ServiceInfo.of(" a")).isEqualTo(ServiceInfo.of("a")); + assertThat(ServiceInfo.of(" !web# ")).isEqualTo(ServiceInfo.of("-web-")); + } + + @Test + void createEmpty() { + checkServiceInfoEmpty(ServiceInfo.empty()); + assertThat(ServiceInfo.empty()) + .isEqualTo(ServiceInfo.empty()); + + } + + @Test + void of() { + checkServiceInfoEmpty(ServiceInfo.of(null)); + checkServiceInfoEmpty(ServiceInfo.of(null, null)); + + checkServiceInfo(ServiceInfo.of("service"), "service", null); + checkServiceInfo(ServiceInfo.of("service", null), "service", null); + checkServiceInfo(ServiceInfo.of("service", "1.2.3"), "service", "1.2.3"); + + } + + @Test + void checkEquality() { + checkEquality(ServiceInfo.of(null), ServiceInfo.empty()); + checkEquality(ServiceInfo.of(""), ServiceInfo.empty()); + checkEquality(ServiceInfo.of(null, null), ServiceInfo.empty()); + checkEquality(ServiceInfo.of("", ""), ServiceInfo.empty()); + } + + private static void checkEquality(ServiceInfo first, ServiceInfo second){ + assertThat(first) + .isEqualTo(second); + + assertThat(first.hashCode()) + .isEqualTo(second.hashCode()); + } + + @Test + void fromManifest() { + checkServiceInfoEmpty(ServiceInfo.fromManifest(null)); + checkServiceInfoEmpty(ServiceInfo.fromManifest(null)); + checkServiceInfoEmpty(ServiceInfo.fromManifest(new Manifest())); + + ServiceInfo serviceInfo = ServiceInfo.fromManifest(manifest(Map.of( + Attributes.Name.IMPLEMENTATION_TITLE.toString(), "service-name" + ))); + checkServiceInfo(serviceInfo, "service-name", null); + + serviceInfo = ServiceInfo.fromManifest(manifest(Map.of( + Attributes.Name.IMPLEMENTATION_TITLE.toString(), "my-service", + Attributes.Name.IMPLEMENTATION_VERSION.toString(), "v42" + ))); + checkServiceInfo(serviceInfo, "my-service", "v42"); + } + + private static Manifest manifest(Map entries) { + Manifest manifest = new Manifest(); + + Attributes attributes = manifest.getMainAttributes(); + entries.forEach(attributes::putValue); + + return manifest; + } + + private static void checkServiceInfoEmpty(ServiceInfo serviceInfo) { + assertThat(serviceInfo.isEmpty()).isTrue(); + assertThat(serviceInfo.getServiceName()).isEqualTo("unknown-java-service"); + assertThat(serviceInfo.hasServiceName()).isFalse(); + assertThat(serviceInfo.getServiceVersion()).isNull(); + + assertThat(serviceInfo).isEqualTo(ServiceInfo.empty()); + } + + private static void checkServiceInfo(ServiceInfo serviceInfo, String expectedServiceName, @Nullable String expectedServiceVersion) { + assertThat(serviceInfo.isEmpty()).isFalse(); + assertThat(serviceInfo.getServiceName()).isEqualTo(expectedServiceName); + assertThat(serviceInfo.hasServiceName()).isTrue(); + if (expectedServiceVersion == null) { + assertThat(serviceInfo.getServiceVersion()).isNull(); + } else { + assertThat(serviceInfo.getServiceVersion()).isEqualTo(expectedServiceVersion); + } + + assertThat(serviceInfo).isEqualTo(ServiceInfo.of(expectedServiceName, expectedServiceVersion)); + } + } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java index 5689160390..113f9328c4 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java @@ -74,7 +74,7 @@ void setUp() { void cleanupAndCheck() { reporter.assertRecycledAfterDecrementingReferences(); objectPoolFactory.checkAllPooledObjectsHaveBeenRecycled(); - tracerImpl.resetServiceNameOverrides(); + tracerImpl.resetServiceInfoOverrides(); } @Test @@ -431,12 +431,15 @@ void testOverrideServiceNameWithoutExplicitServiceName() { .configurationRegistry(SpyConfiguration.createSpyConfig()) .reporter(reporter) .buildAndStart(); - tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden"); - startTestRootTransaction().end(); + ClassLoader cl = getClass().getClassLoader(); + ServiceInfo overridden = ServiceInfo.of("overridden"); + tracer.overrideServiceInfoForClassLoader(cl, overridden); + assertThat(tracer.getServiceInfo(cl)).isEqualTo(overridden); + startTestRootTransaction().end(); - assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden"); + checkServiceInfo(reporter.getFirstTransaction(), overridden); } @Test @@ -448,7 +451,9 @@ void testNotOverrideServiceNameWhenServiceNameConfigured() { .reporter(reporter) .configurationRegistry(localConfig) .buildAndStart(); - tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden"); + ClassLoader cl = getClass().getClassLoader(); + tracer.overrideServiceInfoForClassLoader(cl, ServiceInfo.of("overridden")); + assertThat(tracer.getServiceInfo(cl)).isNull(); startTestRootTransaction().end(); @@ -467,11 +472,18 @@ void testNotOverrideServiceNameWhenDefaultServiceNameConfigured() { .reporter(reporter) .configurationRegistry(localConfig) .buildAndStart(); - tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden"); + + ClassLoader cl = getClass().getClassLoader(); + tracer.overrideServiceInfoForClassLoader(cl, ServiceInfo.of("overridden")); + assertThat(tracer.getServiceInfo(cl)).isNull(); + startTestRootTransaction().end(); CoreConfiguration coreConfig = localConfig.getConfig(CoreConfiguration.class); - assertThat(ServiceInfo.autoDetect(System.getProperties()).getServiceName()).isEqualTo(coreConfig.getServiceName()); + + assertThat(ServiceInfo.autoDetect(System.getProperties())) + .isEqualTo(ServiceInfo.of(coreConfig.getServiceName())); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isNull(); if (command != null) { System.setProperty("sun.java.command", command); @@ -480,6 +492,44 @@ void testNotOverrideServiceNameWhenDefaultServiceNameConfigured() { } } + private static void checkServiceInfo(Transaction transaction, ServiceInfo expected) { + TraceContext traceContext = transaction.getTraceContext(); + assertThat(traceContext.getServiceName()).isEqualTo(expected.getServiceName()); + assertThat(traceContext.getServiceVersion()).isEqualTo(expected.getServiceVersion()); + } + + @Test + void testOverrideServiceVersionWithoutExplicitServiceVersion() { + final ElasticApmTracer tracer = new ElasticApmTracerBuilder() + .reporter(reporter) + .buildAndStart(); + + ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); + tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), overridden); + + startTestRootTransaction().end(); + + checkServiceInfo(reporter.getFirstTransaction(), overridden); + } + + @Test + void testNotOverrideServiceVersionWhenServiceVersionConfigured() { + ConfigurationRegistry localConfig = SpyConfiguration.createSpyConfig(ConfigSources.fromClasspath("test.elasticapm.with-service-version.properties", ClassLoader.getSystemClassLoader())); + final ElasticApmTracer tracer = new ElasticApmTracerBuilder() + .reporter(reporter) + .configurationRegistry(localConfig) + .buildAndStart(); + + ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); + ClassLoader cl = getClass().getClassLoader(); + tracer.overrideServiceInfoForClassLoader(cl, overridden); + assertThat(tracer.getServiceInfo(cl)).isEqualTo(overridden); + + startTestRootTransaction().end(); + + checkServiceInfo(reporter.getFirstTransaction(), overridden); + } + @Test void testCaptureExceptionAndGetErrorId() { Transaction transaction = startTestRootTransaction(); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java index 3fdcda01f4..d6eabb05d8 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java @@ -353,10 +353,26 @@ void testBreakdown_serviceName() { transaction.end(27); tracer.getMetricRegistry().flipPhaseAndReport(metricSets -> { - assertThat(getTimer(metricSets, "span.self_time", "service_name", "app", null).getCount()).isEqualTo(1); - assertThat(getTimer(metricSets, "span.self_time", "service_name", "app", null).getTotalTimeUs()).isEqualTo(15); - assertThat(getTimer(metricSets, "span.self_time", "service_name", "db", "mysql").getCount()).isEqualTo(1); - assertThat(getTimer(metricSets, "span.self_time", "service_name", "db", "mysql").getTotalTimeUs()).isEqualTo(12); + assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "app", null).getCount()).isEqualTo(1); + assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "app", null).getTotalTimeUs()).isEqualTo(15); + assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "db", "mysql").getCount()).isEqualTo(1); + assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "db", "mysql").getTotalTimeUs()).isEqualTo(12); + }); + } + + @Test + void testBreakdown_serviceNameAndVersion() { + final Transaction transaction = createTransaction(); + transaction.getTraceContext().setServiceName("service_name"); + transaction.getTraceContext().setServiceVersion("service_version"); + transaction.createSpan(11).withType("db").withSubtype("mysql").end(23); + transaction.end(27); + + tracer.getMetricRegistry().flipPhaseAndReport(metricSets -> { + assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "app", null).getCount()).isEqualTo(1); + assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "app", null).getTotalTimeUs()).isEqualTo(15); + assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "db", "mysql").getCount()).isEqualTo(1); + assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "db", "mysql").getTotalTimeUs()).isEqualTo(12); }); } @@ -368,13 +384,14 @@ private Transaction createTransaction() { @Nullable private Timer getTimer(Map metricSets, String timerName, @Nullable String spanType, @Nullable String spanSubType) { - return getTimer(metricSets, timerName, null, spanType, spanSubType); + return getTimer(metricSets, timerName, null, null, spanType, spanSubType); } @Nullable - private Timer getTimer(Map metricSets, String timerName, @Nullable String serviceName, @Nullable String spanType, @Nullable String spanSubType) { + private Timer getTimer(Map metricSets, String timerName, @Nullable String serviceName, @Nullable String serviceVersion, @Nullable String spanType, @Nullable String spanSubType) { final MetricSet metricSet = metricSets.get(Labels.Mutable.of() .serviceName(serviceName) + .serviceVersion(serviceVersion) .transactionName("test") .transactionType("request") .spanType(spanType) diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index 48e9adea29..b53de50530 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -733,7 +733,7 @@ void testDeserialization() { traceContext.serialize(serializedContext); TraceContext deserialized = TraceContext.with64BitId(tracer); - deserialized.deserialize(serializedContext, null); + deserialized.deserialize(serializedContext, null, null); assertThat(deserialized.traceIdAndIdEquals(serializedContext)).isTrue(); assertThat(deserialized.getTraceId()).isEqualTo(traceContext.getTraceId()); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java index 116219e093..e1f2d90b08 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java @@ -38,6 +38,9 @@ void testEqualsHashCode() { assertEqualsHashCode( Labels.Mutable.of().serviceName("foo"), Labels.Mutable.of().serviceName("foo")); + assertEqualsHashCode( + Labels.Mutable.of().serviceVersion("foo"), + Labels.Mutable.of().serviceVersion("foo")); assertEqualsHashCode( Labels.Mutable.of().transactionName("foo"), Labels.Mutable.of().transactionName(new StringBuilder("foo"))); 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 dac0b2d6d9..d140d0471b 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 @@ -823,10 +823,12 @@ void testTransactionContextSerialization() { TraceContext ctx = transaction.getTraceContext(); String serviceName = RandomStringUtils.randomAlphabetic(5); + String serviceVersion = RandomStringUtils.randomAlphabetic(5); String frameworkName = RandomStringUtils.randomAlphanumeric(10); String frameworkVersion = RandomStringUtils.randomNumeric(3); ctx.setServiceName(serviceName); + ctx.setServiceVersion(serviceVersion); transaction.setFrameworkName(frameworkName); transaction.setFrameworkVersion(frameworkVersion); @@ -839,6 +841,7 @@ void testTransactionContextSerialization() { assertThat(jsonContext.get("user").get("email").asText()).isEqualTo("user@email.com"); assertThat(jsonContext.get("user").get("username").asText()).isEqualTo("bob"); assertThat(jsonContext.get("service").get("name").asText()).isEqualTo(serviceName); + assertThat(jsonContext.get("service").get("version").asText()).isEqualTo(serviceVersion); assertThat(jsonContext.get("service").get("framework").get("name").asText()).isEqualTo(frameworkName); assertThat(jsonContext.get("service").get("framework").get("version").asText()).isEqualTo(frameworkVersion); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java index 44e05c6f36..d965364c59 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java @@ -19,8 +19,8 @@ package co.elastic.apm.agent.report.serialize; import co.elastic.apm.agent.MockReporter; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.configuration.SpyConfiguration; -import co.elastic.apm.agent.configuration.source.PropertyFileConfigurationSource; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.ElasticApmTracerBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -41,7 +41,7 @@ void testReportedMetricsUseDefaultServiceNameIfServiceNameIsExplicitlySet() thro .configurationRegistry(SpyConfiguration.createSpyConfig(SimpleSource.forTest("service_name", "foo"))) .reporter(reporter) .buildAndStart(); - tracer.overrideServiceNameForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), "MetricRegistryReporterTest"); + tracer.overrideServiceInfoForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), ServiceInfo.of("MetricRegistryReporterTest")); new MetricRegistryReporter(tracer).run(); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java index 7b662ba785..6e679f3806 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.report.serialize; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.metrics.Labels; import co.elastic.apm.agent.metrics.MetricRegistry; import co.elastic.apm.agent.report.ReporterConfiguration; @@ -188,14 +189,29 @@ void testServiceName() throws Exception { assertThat(serviceName.asText()).isEqualTo("bar"); } + @Test + void testServiceNameAndVersion() throws Exception { + registry.updateTimer("foo", Labels.Mutable.of().serviceName("bar").serviceVersion("1.0"), 1); + + JsonNode jsonNode = reportAsJson(); + assertThat(jsonNode).isNotNull(); + JsonNode service = jsonNode.get("metricset").get("service"); + JsonNode serviceName = service.get("name"); + JsonNode serviceVersion = service.get("version"); + assertThat(serviceName.asText()).isEqualTo("bar"); + assertThat(serviceVersion.asText()).isEqualTo("1.0"); + } + @Test void testServiceNameOverrideWithOneService() throws Exception { registry.updateTimer("foo", Labels.Mutable.of(), 1); - JsonNode jsonNode = reportAsJson(singletonList("bar")); + JsonNode jsonNode = reportAsJson(singletonList(ServiceInfo.of("bar", "1.0"))); assertThat(jsonNode).isNotNull(); JsonNode serviceName = jsonNode.get("metricset").get("service").get("name"); assertThat(serviceName.asText()).isEqualTo("bar"); + JsonNode serviceVersion = jsonNode.get("metricset").get("service").get("version"); + assertThat(serviceVersion.asText()).isEqualTo("1.0"); } @Test @@ -204,22 +220,27 @@ void testServiceNameOverrideWithMultipleService() throws Exception { final CompletableFuture jwFuture = new CompletableFuture<>(); registry.flipPhaseAndReport( - metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), List.of("bar1", "bar2"))) + metricSets -> jwFuture.complete(metricRegistrySerializer.serialize( + metricSets.values().iterator().next(), + List.of(ServiceInfo.of("bar1", "2.0"), ServiceInfo.of("bar2")) + )) ); String[] jsonStrings = jwFuture.getNow(null).toString().split("\n"); assertThat(jsonStrings.length).isEqualTo(2); JsonNode jsonNode1 = objectMapper.readTree(jsonStrings[0]); - String serviceName1 = jsonNode1.get("metricset").get("service").get("name").asText(); - assertThat(serviceName1).isEqualTo("bar1"); + JsonNode service1 = jsonNode1.get("metricset").get("service"); + assertThat(service1.get("name").asText()).isEqualTo("bar1"); + assertThat(service1.get("version").asText()).isEqualTo("2.0"); JsonNode samples1 = jsonNode1.get("metricset").get("samples"); assertThat(samples1.get("foo.sum.us").get("value").intValue()).isOne(); assertThat(samples1.get("foo.count").get("value").intValue()).isOne(); JsonNode jsonNode2 = objectMapper.readTree(jsonStrings[1]); - String serviceName2 = jsonNode2.get("metricset").get("service").get("name").asText(); - assertThat(serviceName2).isEqualTo("bar2"); + JsonNode service2 = jsonNode2.get("metricset").get("service"); + assertThat(service2.get("name").asText()).isEqualTo("bar2"); + assertThat(service2.get("version")).isNull(); JsonNode samples2 = jsonNode2.get("metricset").get("samples"); assertThat(samples2.get("foo.sum.us").get("value").intValue()).isOne(); assertThat(samples2.get("foo.count").get("value").intValue()).isOne(); @@ -231,10 +252,10 @@ private JsonNode reportAsJson() throws Exception { } @Nullable - private JsonNode reportAsJson(List serviceNames) throws Exception { + private JsonNode reportAsJson(List serviceInfos) throws Exception { final CompletableFuture jwFuture = new CompletableFuture<>(); registry.flipPhaseAndReport( - metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), serviceNames)) + metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), serviceInfos)) ); JsonNode json = null; JsonWriter jw = jwFuture.getNow(null); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CompletableFutureTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CompletableFutureTest.java index a06d6c32f8..39de0f0905 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CompletableFutureTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CompletableFutureTest.java @@ -69,7 +69,7 @@ void reset() { void testCancelBeforeStart() { final CompletableFuture completableFuture = new CompletableFuture<>(); for (int i = 0; i < NUM_THREADS; i++) { - scheduledExecutorService.schedule(new CompletableFutureTester(completableFuture, 5), 200, TimeUnit.MILLISECONDS); + scheduledExecutorService.schedule(new CompletableFutureTester(completableFuture, 5), 300, TimeUnit.MILLISECONDS); } assertThat(completableFuture.cancel(false)).isTrue(); assertThat(completableFuture.cancel(false)).isFalse(); diff --git a/apm-agent-core/src/test/resources/json-specs/span_types.json b/apm-agent-core/src/test/resources/json-specs/span_types.json index 193489a7a7..aa6e47f56a 100644 --- a/apm-agent-core/src/test/resources/json-specs/span_types.json +++ b/apm-agent-core/src/test/resources/json-specs/span_types.json @@ -225,7 +225,13 @@ "ruby", "java" ] - } + }, + "ldap": { + "__description": "LDAP client", + "__used_by": [ + "java" + ] + } } }, "json": { diff --git a/apm-agent-core/src/test/resources/json-specs/w3c_distributed_tracing.json b/apm-agent-core/src/test/resources/json-specs/w3c_distributed_tracing.json index 05328ca8b8..30a3b0404b 100644 --- a/apm-agent-core/src/test/resources/json-specs/w3c_distributed_tracing.json +++ b/apm-agent-core/src/test/resources/json-specs/w3c_distributed_tracing.json @@ -1,5 +1,5 @@ // -// from https://github.com/w3c/distributed-tracing/blob/master/test/test_data.json +// from https://github.com/w3c/distributed-tracing/blob/main/test/test_data.json // // NOTE: This file is manually copied from the above link and // there is currently NO automation keeping it in sync with the upstream version. diff --git a/apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties b/apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties new file mode 100644 index 0000000000..908f6c963c --- /dev/null +++ b/apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties @@ -0,0 +1 @@ +service_version=TEST_SERVICE_VERSION diff --git a/apm-agent-plugin-sdk/pom.xml b/apm-agent-plugin-sdk/pom.xml index 508298849e..59e2ed1a55 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/pom.xml b/apm-agent-plugins/apm-apache-httpclient-plugin/pom.xml index dcf1a442a1..584c0cec7b 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-apache-httpclient-plugin diff --git a/apm-agent-plugins/apm-api-plugin/pom.xml b/apm-agent-plugins/apm-api-plugin/pom.xml index 3357128604..97dc8778e2 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java index 214b0642de..78183567bc 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java @@ -19,10 +19,12 @@ package co.elastic.apm.api; import co.elastic.apm.AbstractApiTest; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.Scope; import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.TracerInternalApiUtils; import co.elastic.apm.agent.impl.transaction.AbstractSpan; +import co.elastic.apm.agent.impl.transaction.TraceContext; import org.junit.jupiter.api.Test; import java.util.Collections; @@ -327,16 +329,40 @@ void testManualTimestampsDeactivated() { @Test void testOverrideServiceNameForClassLoader() { - tracer.overrideServiceNameForClassLoader(Transaction.class.getClassLoader(), "overridden"); + ServiceInfo overridden = ServiceInfo.of("overridden"); + tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); ElasticApm.startTransaction().end(); - assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden"); + checkTransactionServiceInfo(overridden); } @Test void testOverrideServiceNameForClassLoaderWithRemoteParent() { - tracer.overrideServiceNameForClassLoader(Transaction.class.getClassLoader(), "overridden"); + ServiceInfo overridden = ServiceInfo.of("overridden"); + tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); ElasticApm.startTransactionWithRemoteParent(key -> null).end(); - assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden"); + checkTransactionServiceInfo(overridden); + } + + @Test + void testOverrideServiceVersionForClassLoader() { + ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); + tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); + ElasticApm.startTransaction().end(); + checkTransactionServiceInfo(overridden); + } + + @Test + void testOverrideServiceVersionForClassLoaderWithRemoteParent() { + ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); + tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); + ElasticApm.startTransactionWithRemoteParent(key -> null).end(); + checkTransactionServiceInfo(overridden); + } + + private void checkTransactionServiceInfo(ServiceInfo expected){ + TraceContext traceContext = reporter.getFirstTransaction().getTraceContext(); + assertThat(traceContext.getServiceName()).isEqualTo(expected.getServiceName()); + assertThat(traceContext.getServiceVersion()).isEqualTo(expected.getServiceVersion()); } @Test diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/pom.xml b/apm-agent-plugins/apm-asynchttpclient-plugin/pom.xml index 2aee89f378..20e38ac8b5 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-asynchttpclient-plugin diff --git a/apm-agent-plugins/apm-awslambda-plugin/pom.xml b/apm-agent-plugins/apm-awslambda-plugin/pom.xml index 418906128b..e0f984e866 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 e49fb5457f..dcf994f8e3 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 ec8eb126e6..567f63260b 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 aa6e204d21..526cbc9627 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-cassandra/pom.xml b/apm-agent-plugins/apm-cassandra/pom.xml index f3bfd48e6c..d64faebf6f 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-dubbo-plugin/pom.xml b/apm-agent-plugins/apm-dubbo-plugin/pom.xml index bb7876087c..fa4b3b7285 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AbstractDubboInstrumentationTest.java b/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AbstractDubboInstrumentationTest.java index a44e53ca9d..761115799a 100644 --- a/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AbstractDubboInstrumentationTest.java +++ b/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AbstractDubboInstrumentationTest.java @@ -29,6 +29,7 @@ import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.testutils.TestPort; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -38,19 +39,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; public abstract class AbstractDubboInstrumentationTest extends AbstractInstrumentationTest { - private final int port = TestPort.getAvailableRandomPort(); - private final int anotherPort = TestPort.getAvailableRandomPort(); + private static CoreConfiguration coreConfig; @Nullable - private DubboTestApi dubboTestApi; + private static DubboTestApi dubboTestApi; - static CoreConfiguration coreConfig; + private static int port = -1; @BeforeAll static void initInstrumentation() { @@ -58,34 +59,62 @@ static void initInstrumentation() { } @BeforeEach - void startRootTransaction() { - when(coreConfig.getCaptureBody()).thenReturn(CoreConfiguration.EventType.OFF); + void beforeEach() { + + if (null == dubboTestApi) { + // only start test dubbo once, but with delegation to subclass for creating it + // thus we can't do that in @BeforeAll - // using context classloader is required here - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + port = TestPort.getAvailableRandomPort(); + int backendPort = TestPort.getAvailableRandomPort(); + + assertThat(port).isNotEqualTo(backendPort); + dubboTestApi = buildDubboTestApi(port, backendPort); + } - Transaction transaction = tracer.startRootTransaction(cl); - assertThat(transaction).isNotNull(); - transaction - .withName("dubbo test") - .withType("test") - .withResult("success") - .withOutcome(Outcome.SUCCESS) - .activate(); + when(coreConfig.getCaptureBody()).thenReturn(CoreConfiguration.EventType.OFF); + startTestRootTransaction("dubbo test"); } @AfterEach - void clearReporter() { - tracer.currentTransaction().deactivate().end(); + void afterEach() { + Transaction transaction = tracer.currentTransaction(); + if (transaction != null) { + transaction.deactivate().end(); + } } - protected abstract DubboTestApi buildDubboTestApi(); + @AfterAll + static void doAfterAll(){ + dubboTestApi = null; + } - public DubboTestApi getDubboTestApi() { - if (dubboTestApi == null) { - dubboTestApi = buildDubboTestApi(); + protected static T withRetry(Callable task){ + int count = 10; + while (count > 0) { + try { + return task.call(); + } catch (Exception e) { + count--; + if (count == 0) { + throw new IllegalStateException("unable to start dubbo service", e); + } else { + try { + Thread.sleep(50); + } catch (InterruptedException ex) { + // silently ignored + } + } + } } + throw new IllegalStateException("should not happen"); + } + + protected abstract DubboTestApi buildDubboTestApi(int port1, int port2); + + protected DubboTestApi getDubboTestApi() { + assertThat(dubboTestApi).isNotNull(); return dubboTestApi; } @@ -94,13 +123,14 @@ public void testNormalReturn() { DubboTestApi dubboTestApi = getDubboTestApi(); String normalReturn = dubboTestApi.normalReturn("arg1", 2); assertThat(normalReturn).isEqualTo("arg12"); - List transactions = reporter.getTransactions(); - assertThat(transactions.size()).isEqualTo(1); - validateDubboTransaction(transactions.get(0), DubboTestApi.class, "normalReturn"); - List spans = reporter.getSpans(); - assertThat(spans.size()).isEqualTo(1); - validateDubboSpan(spans.get(0), DubboTestApi.class, "normalReturn"); + // transaction on the receiving side + reporter.awaitTransactionCount(1); + validateDubboTransaction(reporter.getFirstTransaction(), DubboTestApi.class, "normalReturn"); + + // span on the emitting side (outgoing from this method) + reporter.awaitSpanCount(1); + validateDubboSpan(reporter.getFirstSpan(), DubboTestApi.class, "normalReturn", port); List errors = reporter.getErrors(); assertThat(errors.size()).isEqualTo(0); @@ -158,17 +188,17 @@ public void validateDubboTransaction(Transaction transaction, Class apiClass, assertThat(transaction.getType()).isEqualTo("request"); } - protected String getDubboName(Class apiClass, String methodName) { + protected static String getDubboName(Class apiClass, String methodName) { return apiClass.getSimpleName() + "#" + methodName; } - public void validateDubboSpan(Span span, Class apiClass, String methodName) { + public static void validateDubboSpan(Span span, Class apiClass, String methodName, int port) { assertThat(span.getNameAsString()).isEqualTo(getDubboName(apiClass, methodName)); assertThat(span.getType()).isEqualTo("external"); assertThat(span.getSubtype()).isEqualTo("dubbo"); Destination destination = span.getContext().getDestination(); assertThat(destination.getAddress().toString()).isEqualTo("localhost"); - assertThat(destination.getPort()).isEqualTo(getPort()); + assertThat(destination.getPort()).isEqualTo(port); Destination.Service service = destination.getService(); assertThat(service.getResource().toString()).matches("localhost:\\d+"); @@ -178,14 +208,6 @@ public void validateDubboSpan(Span span, Class apiClass, String methodName) { .isNotEqualTo(Outcome.UNKNOWN); } - protected int getPort() { - return port; - } - - protected int getAnotherApiPort() { - return anotherPort; - } - @Test public void testBothProviderAndConsumer() { DubboTestApi dubboTestApi = getDubboTestApi(); diff --git a/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AlibabaDubboInstrumentationTest.java b/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AlibabaDubboInstrumentationTest.java index 89509eb582..e89f8644b1 100644 --- a/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AlibabaDubboInstrumentationTest.java +++ b/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/AlibabaDubboInstrumentationTest.java @@ -37,6 +37,7 @@ import java.util.LinkedList; import java.util.List; +import java.util.concurrent.Callable; import java.util.concurrent.Future; import static org.assertj.core.api.Assertions.assertThat; @@ -45,26 +46,28 @@ public class AlibabaDubboInstrumentationTest extends AbstractDubboInstrumentationTest { @Override - protected DubboTestApi buildDubboTestApi() { + protected DubboTestApi buildDubboTestApi(int port1, int port2) { RegistryConfig registryConfig = createRegistryConfig(); ApplicationConfig appConfig = createApplicationConfig(); //build AnotherApi provider - ProtocolConfig anotherApiProtocolConfig = createProtocolConfig(getAnotherApiPort()); + ProtocolConfig anotherApiProtocolConfig = createProtocolConfig(port2); createAndExportServiceConfig(registryConfig, AnotherApi.class, new AnotherApiImpl(), appConfig, anotherApiProtocolConfig); //build AnotherApi consumer - ReferenceConfig anotherApiReferenceConfig = createReferenceConfig(AnotherApi.class, appConfig, anotherApiProtocolConfig.getPort()); + ReferenceConfig anotherApiReferenceConfig = createReferenceConfig(AnotherApi.class, appConfig, port2); + + AnotherApi anotherApi = withRetry(anotherApiReferenceConfig::get); // build DubboTestApi provider - ProtocolConfig protocolConfig = createProtocolConfig(getPort()); - createAndExportServiceConfig(registryConfig, DubboTestApi.class, new DubboTestApiImpl(anotherApiReferenceConfig.get()), appConfig, protocolConfig); + ProtocolConfig protocolConfig = createProtocolConfig(port1); + createAndExportServiceConfig(registryConfig, DubboTestApi.class, new DubboTestApiImpl(anotherApi), appConfig, protocolConfig); // build DubboTestApi consumer - ReferenceConfig testApiReferenceConfig = createReferenceConfig(DubboTestApi.class, appConfig, protocolConfig.getPort()); + ReferenceConfig dubboTestApi = createReferenceConfig(DubboTestApi.class, appConfig, port1); List methodConfigList = new LinkedList<>(); - testApiReferenceConfig.setMethods(methodConfigList); + dubboTestApi.setMethods(methodConfigList); MethodConfig asyncConfig = new MethodConfig(); asyncConfig.setName("async"); asyncConfig.setAsync(true); @@ -76,7 +79,7 @@ protected DubboTestApi buildDubboTestApi() { asyncNoReturnConfig.setReturn(false); methodConfigList.add(asyncNoReturnConfig); - return testApiReferenceConfig.get(); + return dubboTestApi.get(); } private static RegistryConfig createRegistryConfig() { @@ -111,7 +114,13 @@ private static void createAndExportServiceConfig(RegistryConfig registryConf serviceConfig.setInterface(interfaceClass); serviceConfig.setRef(interfaceImpl); serviceConfig.setRegistry(registryConfig); - serviceConfig.export(); + + withRetry((Callable) () -> { + serviceConfig.export(); + return null; + }); + + } private static ReferenceConfig createReferenceConfig(Class interfaceClass, ApplicationConfig applicationConfig, int port) { diff --git a/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/ApacheDubboInstrumentationTest.java b/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/ApacheDubboInstrumentationTest.java index 3f624f19a7..f361080c7e 100644 --- a/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/ApacheDubboInstrumentationTest.java +++ b/apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/ApacheDubboInstrumentationTest.java @@ -45,26 +45,29 @@ public class ApacheDubboInstrumentationTest extends AbstractDubboInstrumentationTest { @Override - protected DubboTestApi buildDubboTestApi() { + protected DubboTestApi buildDubboTestApi(int port1, int port2) { + RegistryConfig registryConfig = createRegistryConfig(); ApplicationConfig appConfig = createApplicationConfig(); //build AnotherApi provider - ProtocolConfig anotherApiProtocolConfig = createProtocolConfig(getAnotherApiPort()); + ProtocolConfig anotherApiProtocolConfig = createProtocolConfig(port2); createAndExportServiceConfig(registryConfig, AnotherApi.class, new AnotherApiImpl(), appConfig, anotherApiProtocolConfig); // build AnotherApi consumer - ReferenceConfig anotherApiReferenceConfig = createReferenceConfig(AnotherApi.class, appConfig, anotherApiProtocolConfig.getPort()); + ReferenceConfig anotherApiReferenceConfig = createReferenceConfig(AnotherApi.class, appConfig, port2); + + AnotherApi anotherApi = withRetry(anotherApiReferenceConfig::get); // build DubboTestApi provider - ProtocolConfig protocolConfig = createProtocolConfig(getPort()); - createAndExportServiceConfig(registryConfig, DubboTestApi.class, new DubboTestApiImpl(anotherApiReferenceConfig.get()), appConfig, protocolConfig); + ProtocolConfig protocolConfig = createProtocolConfig(port1); + createAndExportServiceConfig(registryConfig, DubboTestApi.class, new DubboTestApiImpl(anotherApi), appConfig, protocolConfig); // build DubboTestApi consumer - ReferenceConfig testApiReferenceConfig = createReferenceConfig(DubboTestApi.class, appConfig, protocolConfig.getPort()); + ReferenceConfig dubboTestApi = createReferenceConfig(DubboTestApi.class, appConfig, port1); List methodConfigList = new LinkedList<>(); - testApiReferenceConfig.setMethods(methodConfigList); + dubboTestApi.setMethods(methodConfigList); MethodConfig asyncConfig = new MethodConfig(); asyncConfig.setName("async"); @@ -77,7 +80,7 @@ protected DubboTestApi buildDubboTestApi() { asyncNoReturnConfig.setReturn(false); methodConfigList.add(asyncNoReturnConfig); - return testApiReferenceConfig.get(); + return dubboTestApi.get(); } private static RegistryConfig createRegistryConfig() { @@ -112,7 +115,12 @@ private static void createAndExportServiceConfig(RegistryConfig registryConf serviceConfig.setInterface(interfaceClass); serviceConfig.setRef(interfaceImpl); serviceConfig.setRegistry(registryConfig); - serviceConfig.export(); + + withRetry(() -> { + serviceConfig.export(); + return null; + }); + } private static ReferenceConfig createReferenceConfig(Class interfaceClass, ApplicationConfig applicationConfig, int port) { diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml new file mode 100644 index 0000000000..0bd3c0f80e --- /dev/null +++ b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + apm-agent-plugins + co.elastic.apm + 1.29.1-SNAPSHOT + + + apm-ecs-logging-plugin + ${project.groupId}:${project.artifactId} + + + ${project.basedir}/../.. + + + + + co.elastic.logging + log4j-ecs-layout + 1.2.0 + provided + + + co.elastic.logging + log4j2-ecs-layout + 1.2.0 + provided + + + org.apache.logging.log4j + log4j-core + 2.14.1 + provided + + + + diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java new file mode 100644 index 0000000000..ac11702d62 --- /dev/null +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java @@ -0,0 +1,74 @@ +/* + * 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.ecs_logging; + +import co.elastic.apm.agent.bci.TracerAwareInstrumentation; +import co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers; +import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.GlobalTracer; +import co.elastic.logging.log4j2.EcsLayout; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.Arrays; +import java.util.Collection; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +public class Log4j2ServiceNameInstrumentation extends TracerAwareInstrumentation { + + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + return not(CustomElementMatchers.isAgentClassLoader()); + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("co.elastic.logging.log4j2.EcsLayout$Builder"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("build"); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("logging", "log4j2-ecs"); + } + + public static class AdviceClass { + + private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl(); + + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void onEnter(@Advice.This EcsLayout.Builder builder) { + if (builder.getServiceName() == null || builder.getServiceName().isEmpty()) { + ServiceInfo serviceInfo = tracer.getServiceInfo(Thread.currentThread().getContextClassLoader()); + String configuredServiceName = tracer.getConfig(CoreConfiguration.class).getServiceName(); + builder.setServiceName(serviceInfo != null ? serviceInfo.getServiceName() : configuredServiceName); + } + } + } +} diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation new file mode 100644 index 0000000000..9ee68e9e14 --- /dev/null +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -0,0 +1 @@ +co.elastic.apm.agent.ecs_logging.Log4j2ServiceNameInstrumentation diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java new file mode 100644 index 0000000000..72b885bfb4 --- /dev/null +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java @@ -0,0 +1,62 @@ +/* + * 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.ecs_logging; + +import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.logging.log4j2.EcsLayout; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +class Log4j2ServiceNameInstrumentationTest extends AbstractInstrumentationTest { + + @BeforeAll + static void setUp() { + when(tracer.getConfig(CoreConfiguration.class).getServiceName()).thenReturn("foo"); + } + + @Test + void testBuildWithNoServiceNameSet() throws JsonProcessingException { + EcsLayout ecsLayout = EcsLayout.newBuilder().build(); + assertThat(getServiceName(ecsLayout.toSerializable(createLogEvent()))).isEqualTo("foo"); + } + + @Test + void testBuildWithServiceNameSet() throws JsonProcessingException { + EcsLayout ecsLayout = EcsLayout.newBuilder().setServiceName("bar").build(); + assertThat(getServiceName(ecsLayout.toSerializable(createLogEvent()))).isEqualTo("bar"); + } + + private static Log4jLogEvent createLogEvent() { + return new Log4jLogEvent("", null, "", null, new SimpleMessage(), null, null); + } + + private static String getServiceName(String json) throws JsonProcessingException { + return (String) new ObjectMapper().readValue(json, Map.class).get("service.name"); + } +} diff --git a/apm-agent-plugins/apm-error-logging-plugin/pom.xml b/apm-agent-plugins/apm-error-logging-plugin/pom.xml index 0bafdb763b..d1dbc2fbf0 100644 --- a/apm-agent-plugins/apm-error-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-error-logging-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-error-logging-plugin 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 d1e1f67c9a..0cebba5ab4 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 fec2171460..db7e3854b2 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 8539b4be72..b9cec05f19 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-es-restclient-plugin-7_x 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 4d48a6a8f1..703dd61089 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 bf0301949c..bd54ada6fa 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-es-restclient-plugin diff --git a/apm-agent-plugins/apm-grails-plugin/pom.xml b/apm-agent-plugins/apm-grails-plugin/pom.xml index aeb4c004ae..9f7941c797 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 7925d6a17d..a274dab997 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 cd63cbf7c2..deb6826a5e 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 f92fcebd4d..7ae82986a7 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-grpc-test-latest diff --git a/apm-agent-plugins/apm-grpc/pom.xml b/apm-agent-plugins/apm-grpc/pom.xml index bbddcfcb44..0939e0ca88 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 4233eb0c58..9b3d82c62a 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 5eda5aac42..115329f8d2 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 3714a0727d..96cf1f7aa8 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 4c3b7de56e..446d8fe5b2 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 235c7d85e5..a010da12ac 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-httpclient-core diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml b/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml new file mode 100644 index 0000000000..302a6d3045 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + + apm-agent-plugins + co.elastic.apm + 1.29.1-SNAPSHOT + + + apm-jakarta-websocket-plugin + ${project.groupId}:${project.artifactId} + + + ${project.basedir}/../.. + + + + + javax.websocket + javax.websocket-api + 1.1 + provided + + + jakarta.websocket + jakarta.websocket-api + 2.0.0 + provided + + + + diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java new file mode 100644 index 0000000000..279b630586 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java @@ -0,0 +1,119 @@ +/* + * 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.websocket; + +import co.elastic.apm.agent.bci.TracerAwareInstrumentation; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; +import co.elastic.apm.agent.impl.transaction.Outcome; +import co.elastic.apm.agent.impl.transaction.Transaction; +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 net.bytebuddy.matcher.ElementMatchers; + +import javax.annotation.Nullable; +import java.util.Collection; + +import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; +import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.isInAnyPackage; +import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_HIGH_LEVEL_FRAMEWORK; +import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; +import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.not; + +public abstract class BaseServerEndpointInstrumentation extends TracerAwareInstrumentation { + + private final Collection applicationPackages; + + public BaseServerEndpointInstrumentation(ElasticApmTracer tracer) { + applicationPackages = tracer.getConfig(StacktraceConfiguration.class).getApplicationPackages(); + } + + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + return not(isBootstrapClassLoader()) + .and(classLoaderCanLoadClass(getServerEndpointClassName())); + } + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return isInAnyPackage(applicationPackages, ElementMatchers.any()); + } + + @Override + public ElementMatcher getTypeMatcher() { + return isAnnotatedWith(named(getServerEndpointClassName())); + } + + @Override + public ElementMatcher getMethodMatcher() { + return isAnnotatedWith( + namedOneOf("javax.websocket.OnOpen", "jakarta.websocket.OnOpen") + .or(namedOneOf("javax.websocket.OnMessage", "jakarta.websocket.OnMessage")) + .or(namedOneOf("javax.websocket.OnError", "jakarta.websocket.OnError")) + .or(namedOneOf("javax.websocket.OnClose", "jakarta.websocket.OnClose"))); + } + + protected abstract String getServerEndpointClassName(); + + protected static class BaseAdvice { + + @Nullable + protected static Object startTransactionOrSetTransactionName(String signature, String frameworkName, @Nullable String frameworkVersion) { + Transaction currentTransaction = tracer.currentTransaction(); + if (currentTransaction == null) { + Transaction rootTransaction = tracer.startRootTransaction(Thread.currentThread().getContextClassLoader()); + if (rootTransaction != null) { + setTransactionName(rootTransaction, signature, frameworkName, frameworkVersion); + return rootTransaction.activate(); + } + } else { + setTransactionName(currentTransaction, signature, frameworkName, frameworkVersion); + } + + return null; + } + + protected static void endTransaction(@Nullable Object transactionOrNull, @Advice.Thrown @Nullable Throwable t) { + if (transactionOrNull == null) { + return; + } + + Transaction transaction = (Transaction) transactionOrNull; + try { + if (t != null) { + transaction.captureException(t).withOutcome(Outcome.FAILURE); + } + } finally { + transaction.deactivate().end(); + } + } + + private static void setTransactionName(Transaction transaction, String signature, String frameworkName, @Nullable String frameworkVersion) { + transaction.withName(signature, PRIO_HIGH_LEVEL_FRAMEWORK, false); + transaction.setFrameworkName(frameworkName); + transaction.setFrameworkVersion(frameworkVersion); + } + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.java new file mode 100644 index 0000000000..6219ca5809 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.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.websocket; + +import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.util.VersionUtils; +import jakarta.websocket.server.ServerEndpoint; +import net.bytebuddy.asm.Advice; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; + +public class JakartaServerEndpointInstrumentation extends BaseServerEndpointInstrumentation { + + public JakartaServerEndpointInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("websocket", "jakarta-websocket"); + } + + @Override + protected String getServerEndpointClassName() { + return "jakarta.websocket.server.ServerEndpoint"; + } + + public static class AdviceClass extends BaseAdvice { + + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static Object onMethodEnter(@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature) { + String frameworkVersion = VersionUtils.getVersion(ServerEndpoint.class, "jakarta.websocket", "jakarta.websocket-api"); + return startTransactionOrSetTransactionName(signature, "Jakarta WebSocket", frameworkVersion); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) + public static void onMethodExit(@Advice.Enter @Nullable Object transactionOrNull, @Advice.Thrown @Nullable Throwable t) { + endTransaction(transactionOrNull, t); + } + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.java new file mode 100644 index 0000000000..4886047df3 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.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.websocket; + +import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.util.VersionUtils; +import net.bytebuddy.asm.Advice; + +import javax.annotation.Nullable; +import javax.websocket.server.ServerEndpoint; +import java.util.Arrays; +import java.util.Collection; + +public class JavaxServerEndpointInstrumentation extends BaseServerEndpointInstrumentation { + + public JavaxServerEndpointInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("websocket", "javax-websocket"); + } + + @Override + protected String getServerEndpointClassName() { + return "javax.websocket.server.ServerEndpoint"; + } + + public static class AdviceClass extends BaseServerEndpointInstrumentation.BaseAdvice { + + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static Object onMethodEnter(@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature) { + String frameworkVersion = VersionUtils.getVersion(ServerEndpoint.class, "javax.websocket", "javax.websocket-api"); + return startTransactionOrSetTransactionName(signature, "Java WebSocket", frameworkVersion); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) + public static void onMethodExit(@Advice.Enter @Nullable Object transactionOrNull, @Advice.Thrown @Nullable Throwable t) { + endTransaction(transactionOrNull, t); + } + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation new file mode 100644 index 0000000000..8ae73700f6 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -0,0 +1,2 @@ +co.elastic.apm.agent.websocket.JakartaServerEndpointInstrumentation +co.elastic.apm.agent.websocket.JavaxServerEndpointInstrumentation diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java new file mode 100644 index 0000000000..375ce6ae92 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java @@ -0,0 +1,103 @@ +/* + * 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.websocket; + +import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.websocket.endpoint.WebSocketEndpoint; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +abstract class BaseServerEndpointInstrumentationTest extends AbstractInstrumentationTest { + + private final WebSocketEndpoint serverEndpoint; + + protected BaseServerEndpointInstrumentationTest(WebSocketEndpoint serverEndpoint) { + this.serverEndpoint = serverEndpoint; + } + + @Test + void testOnOpenWithActiveTransaction() { + Transaction transaction = startTestRootTransaction(); + try { + serverEndpoint.onOpen(); + } finally { + transaction.deactivate().end(); + } + + assertReportedTransactionNameAndFramework("onOpen"); + } + + @Test + void testOnOpenWithoutActiveTransaction() { + serverEndpoint.onOpen(); + + assertReportedTransactionNameAndFramework("onOpen"); + } + + @Test + void testOnMessage() { + Transaction transaction = startTestRootTransaction(); + try { + serverEndpoint.onMessage(""); + } finally { + transaction.deactivate().end(); + } + + assertReportedTransactionNameAndFramework("onMessage"); + } + + @Test + void testOnError() { + Transaction transaction = startTestRootTransaction(); + try { + serverEndpoint.onError(); + } finally { + transaction.deactivate().end(); + } + + assertReportedTransactionNameAndFramework("onError"); + } + + @Test + void testOnClose() { + Transaction transaction = startTestRootTransaction(); + try { + serverEndpoint.onClose(); + } finally { + transaction.deactivate().end(); + } + + assertReportedTransactionNameAndFramework("onClose"); + } + + protected abstract String getWebSocketServerEndpointClassName(); + + protected abstract String getFrameworkName(); + + protected abstract String getFrameworkVersion(); + + private void assertReportedTransactionNameAndFramework(String methodName) { + Transaction transaction = reporter.getFirstTransaction(); + assertThat(transaction.getNameAsString()).isEqualTo(getWebSocketServerEndpointClassName() + '#' + methodName); + assertThat(transaction.getFrameworkName()).isEqualTo(getFrameworkName()); + assertThat(transaction.getFrameworkVersion()).isEqualTo(getFrameworkVersion()); + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java new file mode 100644 index 0000000000..2c973a81a8 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java @@ -0,0 +1,43 @@ +/* + * 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.websocket; + +import co.elastic.apm.agent.websocket.endpoint.JakartaServerEndpoint; + +class JakartaServerEndpointInstrumentationTest extends BaseServerEndpointInstrumentationTest { + + JakartaServerEndpointInstrumentationTest() { + super(new JakartaServerEndpoint()); + } + + @Override + protected String getWebSocketServerEndpointClassName() { + return JakartaServerEndpoint.class.getSimpleName(); + } + + @Override + protected String getFrameworkName() { + return "Jakarta WebSocket"; + } + + @Override + protected String getFrameworkVersion() { + return "2.0.0"; + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java new file mode 100644 index 0000000000..bd7dde50a3 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java @@ -0,0 +1,43 @@ +/* + * 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.websocket; + +import co.elastic.apm.agent.websocket.endpoint.JavaxServerEndpoint; + +class JavaxServerEndpointInstrumentationTest extends BaseServerEndpointInstrumentationTest { + + JavaxServerEndpointInstrumentationTest() { + super(new JavaxServerEndpoint()); + } + + @Override + protected String getWebSocketServerEndpointClassName() { + return JavaxServerEndpoint.class.getSimpleName(); + } + + @Override + protected String getFrameworkName() { + return "Java WebSocket"; + } + + @Override + protected String getFrameworkVersion() { + return "1.1"; + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java new file mode 100644 index 0000000000..5e4f830deb --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java @@ -0,0 +1,49 @@ +/* + * 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.websocket.endpoint; + +import jakarta.websocket.OnClose; +import jakarta.websocket.OnError; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.server.ServerEndpoint; + +@ServerEndpoint("/path") +public class JakartaServerEndpoint implements WebSocketEndpoint { + + @OnOpen + @Override + public void onOpen() { + } + + @OnMessage + @Override + public void onMessage(String message) { + } + + @OnError + @Override + public void onError() { + } + + @OnClose + @Override + public void onClose() { + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java new file mode 100644 index 0000000000..0b41efc6a2 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java @@ -0,0 +1,49 @@ +/* + * 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.websocket.endpoint; + +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/path") +public class JavaxServerEndpoint implements WebSocketEndpoint { + + @OnOpen + @Override + public void onOpen() { + } + + @OnMessage + @Override + public void onMessage(String message) { + } + + @OnError + @Override + public void onError() { + } + + @OnClose + @Override + public void onClose() { + } +} diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.java new file mode 100644 index 0000000000..f0b2c174e5 --- /dev/null +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.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.websocket.endpoint; + +public interface WebSocketEndpoint { + + void onOpen(); + + void onMessage(String message); + + void onError(); + + void onClose(); +} diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/pom.xml b/apm-agent-plugins/apm-java-concurrent-plugin/pom.xml index 928b9bfbd4..85d1bba7b5 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-java-concurrent-plugin diff --git a/apm-agent-plugins/apm-javalin-plugin/pom.xml b/apm-agent-plugins/apm-javalin-plugin/pom.xml index 5acb229f48..78b3238c4f 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 0d2aca0931..4ceba9c1c0 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-jaxrs-plugin/pom.xml b/apm-agent-plugins/apm-jaxrs-plugin/pom.xml index 69530a75bc..f7f039eae0 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 66a7329b7f..675555795d 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 @@ -18,7 +18,7 @@ jakarta.platform jakarta.jakartaee-api - 9.0.0 + 9.1.0 test diff --git a/apm-agent-plugins/apm-jaxws-plugin/pom.xml b/apm-agent-plugins/apm-jaxws-plugin/pom.xml index b5ade7a5f1..28308c9b57 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-jaxws-plugin diff --git a/apm-agent-plugins/apm-jdbc-plugin/pom.xml b/apm-agent-plugins/apm-jdbc-plugin/pom.xml index a133de401c..110fa846aa 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 0669edae83..cbad9b9e86 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 46b32a153c..3e88bf1742 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-jdk-httpserver-plugin 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 85ccc57f81..1fc3885ffc 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 a7e951c627..5d00865d59 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 5ca0b2bb9c..3f9c60b0f7 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-jms-plugin diff --git a/apm-agent-plugins/apm-jmx-plugin/pom.xml b/apm-agent-plugins/apm-jmx-plugin/pom.xml index 59d4421dc8..d1810cc9f8 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-jmx-plugin diff --git a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java index 8e5c60d19a..52330e467f 100644 --- a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java +++ b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java @@ -94,10 +94,16 @@ private synchronized void tryInit() { if (this.server != null || this.logManagerPropertyPoller != null) { return; } - // Avoid creating the platform MBean server, only get it if already initialized - // otherwise WildFly fails to start with a IllegalStateException: + // Do not eagerly trigger creation of the platform MBean server for known problematic cases + // + // WildFly fails to start with a IllegalStateException: // WFLYLOG0078: The logging subsystem requires the log manager to be org.jboss.logmanager.LogManager - if (setsCustomLogManager()) { + // + // JBoss sets the 'javax.management.builder.initial' system property, but uses the module classloader and + // current thread context class loader to initialize it + // + // Weblogic sets the 'javax.management.builder.initial' system property at runtime + if (setCustomPlatformMBeanServer()) { List servers = MBeanServerFactory.findMBeanServer(null); if (!servers.isEmpty()) { // platform MBean server is already initialized @@ -120,7 +126,7 @@ private MBeanServer getPlatformMBeanServerThreadSafely() { } private void deferInit() { - logger.debug("Deferring initialization of JMX metric tracking until log manager is initialized"); + logger.debug("Deferring initialization of JMX metric tracking until platform mbean server is initialized"); Thread thread = new Thread(new Runnable() { private final long timeout = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5); @@ -131,9 +137,6 @@ public void run() { List servers = MBeanServerFactory.findMBeanServer(null); if (!servers.isEmpty()) { // avoid actively creating a platform mbean server - // because JBoss sets the javax.management.builder.initial system property - // this makes Java create a JBoss specific mbean server that can only be loaded - // when the module class loader is set as the current thread's context class loader init(servers.get(0)); return; } @@ -151,8 +154,9 @@ public void run() { logManagerPropertyPoller = thread; } - private boolean setsCustomLogManager() { - return ClassLoader.getSystemClassLoader().getResource("org/jboss/modules/Main.class") != null; + private boolean setCustomPlatformMBeanServer() { + return ClassLoader.getSystemClassLoader().getResource("org/jboss/modules/Main.class") != null + || System.getProperty("weblogic.Name") != null || System.getProperty("weblogic.home") != null; } synchronized void init(final MBeanServer platformMBeanServer) { diff --git a/apm-agent-plugins/apm-jsf-plugin/pom.xml b/apm-agent-plugins/apm-jsf-plugin/pom.xml index 63b2cdc9b7..c3ac10d548 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-jsf-plugin @@ -25,7 +25,7 @@ jakarta.platform jakarta.jakartaee-api - 9.0.0 + 9.1.0 provided 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 95eec82686..836774b645 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 964237eb3d..eb63b9f22d 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-kafka-plugin/pom.xml b/apm-agent-plugins/apm-kafka-plugin/pom.xml index b2087cad7c..155f636066 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-log-correlation-plugin/pom.xml b/apm-agent-plugins/apm-log-correlation-plugin/pom.xml index 37ee74d0c2..50af18fa39 100644 --- a/apm-agent-plugins/apm-log-correlation-plugin/pom.xml +++ b/apm-agent-plugins/apm-log-correlation-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-log-correlation-plugin diff --git a/apm-agent-plugins/apm-log-shader-plugin/apm-log-shader-plugin-common/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/apm-log-shader-plugin-common/pom.xml index 7125c17f86..9dfddf3610 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/apm-log-shader-plugin-common/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/apm-log-shader-plugin-common/pom.xml @@ -5,7 +5,7 @@ apm-log-shader-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-log-shader-plugin-common diff --git a/apm-agent-plugins/apm-log-shader-plugin/apm-log4j1-plugin/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/apm-log4j1-plugin/pom.xml index 617f770b51..afb3c251bc 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/apm-log4j1-plugin/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/apm-log4j1-plugin/pom.xml @@ -5,7 +5,7 @@ apm-log-shader-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-log4j1-plugin diff --git a/apm-agent-plugins/apm-log-shader-plugin/apm-log4j2-plugin/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/apm-log4j2-plugin/pom.xml index 3a9843cdfe..38fe67cc13 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/apm-log4j2-plugin/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/apm-log4j2-plugin/pom.xml @@ -5,7 +5,7 @@ apm-log-shader-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-log4j2-plugin diff --git a/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml index 23f15a67a1..9f558575dd 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml @@ -5,7 +5,7 @@ apm-logback-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-logback-plugin-impl diff --git a/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml index f948f870bc..0b72418e35 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml @@ -5,7 +5,7 @@ apm-logback-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-logback-plugin-legacy-tests diff --git a/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/pom.xml index 083dc39d64..d52439f019 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/apm-logback-plugin/pom.xml @@ -5,7 +5,7 @@ apm-log-shader-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-logback-plugin diff --git a/apm-agent-plugins/apm-log-shader-plugin/pom.xml b/apm-agent-plugins/apm-log-shader-plugin/pom.xml index 574dc6c49e..10db060eb6 100644 --- a/apm-agent-plugins/apm-log-shader-plugin/pom.xml +++ b/apm-agent-plugins/apm-log-shader-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-log-shader-plugin diff --git a/apm-agent-plugins/apm-log-shipper-plugin/pom.xml b/apm-agent-plugins/apm-log-shipper-plugin/pom.xml index a88197ab19..b13385a153 100644 --- a/apm-agent-plugins/apm-log-shipper-plugin/pom.xml +++ b/apm-agent-plugins/apm-log-shipper-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-log-shipper-plugin diff --git a/apm-agent-plugins/apm-micrometer-plugin/pom.xml b/apm-agent-plugins/apm-micrometer-plugin/pom.xml index ac4013a5b5..63548090ff 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-mongoclient-plugin/pom.xml b/apm-agent-plugins/apm-mongoclient-plugin/pom.xml index 46f6bf291b..0997a5d3ef 100644 --- a/apm-agent-plugins/apm-mongoclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-mongoclient-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-okhttp-plugin/pom.xml b/apm-agent-plugins/apm-okhttp-plugin/pom.xml index 7d7cb60565..18ec29b0c7 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-okhttp-plugin diff --git a/apm-agent-plugins/apm-okhttp-test/pom.xml b/apm-agent-plugins/apm-okhttp-test/pom.xml index 880b44943c..f36e10c091 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-opentracing-plugin/pom.xml b/apm-agent-plugins/apm-opentracing-plugin/pom.xml index 21b951062a..b1523903b1 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-opentracing-plugin diff --git a/apm-agent-plugins/apm-process-plugin/pom.xml b/apm-agent-plugins/apm-process-plugin/pom.xml index fc30bbbfa4..181384059b 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-profiling-plugin/pom.xml b/apm-agent-plugins/apm-profiling-plugin/pom.xml index 47065dee21..e6b5b85971 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-profiling-plugin diff --git a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java index 6decb709a9..c5966f7e3c 100644 --- a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java +++ b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java @@ -148,9 +148,9 @@ private boolean happenedAfter(long timestamp) { return lastSeen < timestamp; } - public static CallTree.Root createRoot(ObjectPool rootPool, byte[] traceContext, @Nullable String serviceName, long nanoTime) { + public static CallTree.Root createRoot(ObjectPool rootPool, byte[] traceContext, @Nullable String serviceName, @Nullable String serviceVersion, long nanoTime) { CallTree.Root root = rootPool.createInstance(); - root.set(traceContext, serviceName, nanoTime); + root.set(traceContext, serviceName, serviceVersion, nanoTime); return root; } @@ -596,9 +596,9 @@ public Root(ElasticApmTracer tracer) { this.rootContext = TraceContext.with64BitId(tracer); } - private void set(byte[] traceContext, @Nullable String serviceName, long nanoTime) { + private void set(byte[] traceContext, @Nullable String serviceName, @Nullable String serviceVersion, long nanoTime) { super.set(null, ROOT_FRAME, nanoTime); - this.rootContext.deserialize(traceContext, serviceName); + this.rootContext.deserialize(traceContext, serviceName, serviceVersion); setActiveSpan(traceContext, nanoTime); } @@ -660,7 +660,7 @@ public void addStackTrace(ElasticApmTracer tracer, List stackTrace, if (activeSpan == null) { firstFrameAfterActivation = true; activeSpan = TraceContext.with64BitId(tracer); - activeSpan.deserialize(activeSpanSerialized, rootContext.getServiceName()); + activeSpan.deserialize(activeSpanSerialized, rootContext.getServiceName(), rootContext.getServiceVersion()); } previousTopOfStack = topOfStack; topOfStack = addFrame(stackTrace, stackTrace.size(), activeSpan, activationTimestamp, nanoTime, callTreePool, minDurationNs, this); diff --git a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java index d288220b13..d4543002fb 100644 --- a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java +++ b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java @@ -777,6 +777,7 @@ private static class ActivationEvent { public static final int SERIALIZED_SIZE = Long.SIZE / Byte.SIZE + // timestamp Short.SIZE / Byte.SIZE + // serviceName index + Short.SIZE / Byte.SIZE + // serviceVersion index TraceContext.SERIALIZED_LENGTH + // traceContextBuffer TraceContext.SERIALIZED_LENGTH + // previousContextBuffer 1 + // rootContext @@ -786,9 +787,14 @@ private static class ActivationEvent { private static final Map serviceNameMap = new HashMap<>(); private static final Map serviceNameBackMap = new HashMap<>(); + private static final Map serviceVersionMap = new HashMap<>(); + private static final Map serviceVersionBackMap = new HashMap<>(); + private long timestamp; @Nullable private String serviceName; + @Nullable + private String serviceVersion; private byte[] traceContextBuffer = new byte[TraceContext.SERIALIZED_LENGTH]; private byte[] previousContextBuffer = new byte[TraceContext.SERIALIZED_LENGTH]; private boolean rootContext; @@ -808,6 +814,7 @@ private void set(TraceContext traceContext, long threadId, boolean activation, @ this.threadId = threadId; this.activation = activation; this.serviceName = traceContext.getServiceName(); + this.serviceVersion = traceContext.getServiceVersion(); if (previousContext != null) { previousContext.serialize(previousContextBuffer); rootContext = false; @@ -845,7 +852,7 @@ private void handleActivationEvent(SamplingProfiler samplingProfiler) { } private void startProfiling(SamplingProfiler samplingProfiler) { - CallTree.Root root = CallTree.createRoot(samplingProfiler.rootPool, traceContextBuffer, serviceName, timestamp); + CallTree.Root root = CallTree.createRoot(samplingProfiler.rootPool, traceContextBuffer, serviceName, serviceVersion, timestamp); if (logger.isDebugEnabled()) { logger.debug("Create call tree ({}) for thread {}", deserialize(samplingProfiler, traceContextBuffer), threadId); } @@ -860,7 +867,7 @@ private void startProfiling(SamplingProfiler samplingProfiler) { } private TraceContext deserialize(SamplingProfiler samplingProfiler, byte[] traceContextBuffer) { - samplingProfiler.contextForLogging.deserialize(traceContextBuffer, null); + samplingProfiler.contextForLogging.deserialize(traceContextBuffer, null, null); return samplingProfiler.contextForLogging; } @@ -906,6 +913,7 @@ private void stopProfiling(SamplingProfiler samplingProfiler) { public void serialize(ByteBuffer buf) { buf.putLong(timestamp); buf.putShort(getServiceNameIndex()); + buf.putShort(getServiceVersionIndex()); buf.put(traceContextBuffer); buf.put(previousContextBuffer); buf.put(rootContext ? (byte) 1 : (byte) 0); @@ -916,6 +924,7 @@ public void serialize(ByteBuffer buf) { public void deserialize(ByteBuffer buf) { timestamp = buf.getLong(); serviceName = serviceNameBackMap.get(buf.getShort()); + serviceVersion = serviceVersionBackMap.get(buf.getShort()); buf.get(traceContextBuffer); buf.get(previousContextBuffer); rootContext = buf.get() == 1; @@ -932,6 +941,16 @@ private short getServiceNameIndex() { } return index; } + + private short getServiceVersionIndex() { + Short index = serviceVersionMap.get(serviceVersion); + if (index == null) { + index = (short) serviceVersionMap.size(); + serviceVersionMap.put(serviceVersion, index); + serviceVersionBackMap.put(index, serviceVersion); + } + return index; + } } /** diff --git a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java index dbee90ae42..cad43b38b0 100644 --- a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java +++ b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java @@ -104,7 +104,7 @@ void testSpanification() throws Exception { @Test void testCallTreeWithActiveSpan() { TraceContext rootContext = CallTreeTest.rootTraceContext(tracer); - CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), rootContext.serialize(), rootContext.getServiceName(), 0); + CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), rootContext.serialize(), rootContext.getServiceName(), rootContext.getServiceVersion(),0); NoopObjectPool callTreePool = NoopObjectPool.ofRecyclable(CallTree::new); root.addStackTrace(tracer, List.of(StackFrame.of("A", "a")), 0, callTreePool, 0); diff --git a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java index 877590a129..0da789fc61 100644 --- a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java +++ b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java @@ -85,7 +85,7 @@ void tearDown() throws IOException { @Test void testCallTree() { TraceContext traceContext = TraceContext.with64BitId(MockTracer.create()); - CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), traceContext.serialize(), traceContext.getServiceName(), 0); + CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), traceContext.serialize(), traceContext.getServiceName(), traceContext.getServiceVersion(), 0); ObjectPool callTreePool = ListBasedObjectPool.ofRecyclable(new ArrayList<>(), Integer.MAX_VALUE, CallTree::new); root.addStackTrace(tracer, List.of(StackFrame.of("A", "a")), 0, callTreePool, 0); root.addStackTrace(tracer, List.of(StackFrame.of("A", "b"), StackFrame.of("A", "a")), TimeUnit.MILLISECONDS.toNanos(10), callTreePool, 0); diff --git a/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-1-plugin/pom.xml b/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-1-plugin/pom.xml index 5553999ac0..bc8e380a37 100644 --- a/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-1-plugin/pom.xml +++ b/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-1-plugin/pom.xml @@ -3,7 +3,7 @@ apm-quartz-job-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-2-plugin/pom.xml b/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-2-plugin/pom.xml index 6b81d943d6..5e0b958ff6 100644 --- a/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-2-plugin/pom.xml +++ b/apm-agent-plugins/apm-quartz-job-plugin/apm-quartz-2-plugin/pom.xml @@ -3,7 +3,7 @@ apm-quartz-job-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/apm-agent-plugins/apm-quartz-job-plugin/pom.xml b/apm-agent-plugins/apm-quartz-job-plugin/pom.xml index 20c421972a..e0c213bb38 100644 --- a/apm-agent-plugins/apm-quartz-job-plugin/pom.xml +++ b/apm-agent-plugins/apm-quartz-job-plugin/pom.xml @@ -11,7 +11,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-quartz-job-plugin diff --git a/apm-agent-plugins/apm-quartz-job-plugin/quartz-common/pom.xml b/apm-agent-plugins/apm-quartz-job-plugin/quartz-common/pom.xml index 2b49fcbf42..46ea522d5f 100644 --- a/apm-agent-plugins/apm-quartz-job-plugin/quartz-common/pom.xml +++ b/apm-agent-plugins/apm-quartz-job-plugin/quartz-common/pom.xml @@ -3,7 +3,7 @@ apm-quartz-job-plugin co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 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 8da3a673f8..78709c8884 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 57e181f3d5..71aaece9cb 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 f939ac7f3d..096dd7d188 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 bfb3b87cd7..f55cb46c4d 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-rabbitmq-test-4 diff --git a/apm-agent-plugins/apm-rabbitmq/pom.xml b/apm-agent-plugins/apm-rabbitmq/pom.xml index 27f86deed8..61f3638e49 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-rabbitmq diff --git a/apm-agent-plugins/apm-reactor-plugin/pom.xml b/apm-agent-plugins/apm-reactor-plugin/pom.xml index 2016c92b99..87625cb913 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 cea22a0cfb..a3c7aa0a56 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 63145aa396..f3f75c9c2a 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-jedis-3-tests 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 ae12d93d1d..514478a2d7 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 a7913bd50a..35222ade43 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 29d5a31853..e3daf5dd85 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 564940f163..2bf3ce50e2 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 afc4459a06..064cb8a48f 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-redisson-plugin diff --git a/apm-agent-plugins/apm-redis-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/pom.xml index 31eb3559f0..f86ad01f1f 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 4da5f105eb..7be4890c5f 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-scala-concurrent-plugin @@ -36,7 +36,7 @@ net.alchim31.maven scala-maven-plugin - 4.3.1 + 4.5.6 2.13.2 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 d9df6ef25b..362f2727f7 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 @@ -18,7 +18,7 @@ jakarta.platform jakarta.jakartaee-api - 9.0.0 + 9.1.0 test diff --git a/apm-agent-plugins/apm-scheduled-annotation-plugin/pom.xml b/apm-agent-plugins/apm-scheduled-annotation-plugin/pom.xml index a4478629e5..63f9cedfb5 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-scheduled-annotation-plugin diff --git a/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml b/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml index 0bef53e177..5b051a325d 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-servlet-jakarta-test @@ -62,12 +62,12 @@ org.slf4j slf4j-api - 2.0.0-alpha0 + 1.7.35 org.slf4j slf4j-simple - 2.0.0-alpha0 + 1.7.35 diff --git a/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java b/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java index ba16a36b42..52b405e3a8 100644 --- a/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java +++ b/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java @@ -46,7 +46,7 @@ abstract class AbstractServletTest extends AbstractInstrumentationTest { void initServerAndClient() throws Exception { // because we reuse the same classloader with different servlet context names // we need to explicitly reset the name cache to make service name detection work as expected - ServletGlobalState.clearServiceNameCache(); + ServletServiceNameHelper.clearServiceNameCache(); // server is not reused between tests as handler is provided from subclass // another alternative diff --git a/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/JakartaServletInstrumentationTest.java b/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/JakartaServletInstrumentationTest.java index c4e1536c23..cf4eef1666 100644 --- a/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/JakartaServletInstrumentationTest.java +++ b/apm-agent-plugins/apm-servlet-jakarta-test/src/test/java/co/elastic/apm/agent/servlet/JakartaServletInstrumentationTest.java @@ -43,7 +43,6 @@ import java.io.IOException; import java.io.PrintWriter; -import java.util.Collections; import java.util.EnumSet; import static co.elastic.apm.agent.servlet.RequestDispatcherSpanType.FORWARD; diff --git a/apm-agent-plugins/apm-servlet-plugin/pom.xml b/apm-agent-plugins/apm-servlet-plugin/pom.xml index 986a776aa6..f3b3d9fb7d 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-servlet-plugin diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JakartaServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JakartaServletApiAdvice.java index 8bf60749c0..88aa718ead 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JakartaServletApiAdvice.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JakartaServletApiAdvice.java @@ -18,41 +18,21 @@ */ package co.elastic.apm.agent.servlet; -import co.elastic.apm.agent.impl.GlobalTracer; -import co.elastic.apm.agent.impl.context.Request; -import co.elastic.apm.agent.impl.transaction.Transaction; -import co.elastic.apm.agent.servlet.helper.JakartaServletTransactionCreationHelper; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.RequestDispatcher; -import jakarta.servlet.ServletContext; +import co.elastic.apm.agent.servlet.adapter.JakartaServletApiAdapter; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; import javax.annotation.Nullable; -import java.security.Principal; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Map; -public class JakartaServletApiAdvice extends ServletApiAdvice implements ServletHelper { +public class JakartaServletApiAdvice extends ServletApiAdvice { - private static JakartaServletTransactionCreationHelper transactionCreationHelper; - private static JakartaServletApiAdvice helper; - - static { - transactionCreationHelper = new JakartaServletTransactionCreationHelper(GlobalTracer.requireTracerImpl()); - helper = new JakartaServletApiAdvice(); - } + private static final JakartaServletApiAdapter adapter = JakartaServletApiAdapter.get(); @Nullable @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object onEnterServletService(@Advice.Argument(0) ServletRequest servletRequest) { - return onServletEnter(servletRequest, helper); + return onServletEnter(adapter, servletRequest); } @@ -62,201 +42,6 @@ public static void onExitServletService(@Advice.Argument(0) ServletRequest servl @Advice.Enter @Nullable Object transactionOrScopeOrSpan, @Advice.Thrown @Nullable Throwable t, @Advice.This Object thiz) { - onExitServlet(servletRequest, servletResponse, transactionOrScopeOrSpan, t, thiz, helper); - } - - @Override - public boolean isHttpServletRequest(ServletRequest servletRequest) { - return servletRequest instanceof HttpServletRequest; - } - - @Override - public boolean isHttpServletResponse(ServletResponse servletResponse) { - return servletResponse instanceof HttpServletResponse; - } - - @Override - public boolean isRequestDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.REQUEST; - } - - @Override - public boolean isAsyncDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.ASYNC; - } - - @Override - public boolean isForwardDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.FORWARD; - } - - @Override - public boolean isIncludeDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.INCLUDE; - } - - @Override - public boolean isErrorDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.ERROR; - } - - @Override - public ClassLoader getClassloader(ServletContext servletContext) { - return transactionCreationHelper.getClassloader(servletContext); - } - - @Override - public String getServletContextName(ServletContext servletContext) { - return servletContext.getServletContextName(); - } - - @Override - public String getContextPath(ServletContext servletContext) { - return servletContext.getContextPath(); - } - - @Override - public Transaction createAndActivateTransaction(HttpServletRequest httpServletRequest) { - return transactionCreationHelper.createAndActivateTransaction(httpServletRequest); - } - - @Override - public void handleCookies(Request request, HttpServletRequest servletRequest) { - if (servletRequest.getCookies() != null) { - for (Cookie cookie : servletRequest.getCookies()) { - request.addCookie(cookie.getName(), cookie.getValue()); - } - } - } - - @Override - public Enumeration getRequestHeaderNames(HttpServletRequest servletRequest) { - return servletRequest.getHeaderNames(); - } - - @Override - public Enumeration getRequestHeaders(HttpServletRequest servletRequest, String name) { - return servletRequest.getHeaders(name); - } - - @Override - public String getHeader(HttpServletRequest httpServletRequest, String name) { - return httpServletRequest.getHeader(name); - } - - @Override - public String getProtocol(HttpServletRequest servletRequest) { - return servletRequest.getProtocol(); - } - - @Override - public String getMethod(HttpServletRequest servletRequest) { - return servletRequest.getMethod(); - } - - @Override - public boolean isSecure(HttpServletRequest servletRequest) { - return servletRequest.isSecure(); - } - - @Override - public String getScheme(HttpServletRequest servletRequest) { - return servletRequest.getScheme(); - } - - @Override - public String getServerName(HttpServletRequest servletRequest) { - return servletRequest.getServerName(); - } - - @Override - public int getServerPort(HttpServletRequest servletRequest) { - return servletRequest.getServerPort(); - } - - @Override - public String getRequestURI(HttpServletRequest servletRequest) { - return servletRequest.getRequestURI(); - } - - @Override - public String getQueryString(HttpServletRequest servletRequest) { - return servletRequest.getQueryString(); - } - - @Override - public String getRemoteAddr(HttpServletRequest servletRequest) { - return servletRequest.getRemoteAddr(); - } - - @Override - public String getServletPath(HttpServletRequest servletRequest) { - return servletRequest.getServletPath(); - } - - @Override - public String getPathInfo(HttpServletRequest servletRequest) { - return servletRequest.getPathInfo(); - } - - @Override - public Object getIncludeServletPathAttribute(HttpServletRequest servletRequest) { - return servletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); - } - - @Override - public Object getIncludePathInfoAttribute(HttpServletRequest servletRequest) { - return servletRequest.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); - } - - @Override - public boolean isInstanceOfHttpServlet(Object object) { - return object instanceof HttpServlet; - } - - @Override - public Principal getUserPrincipal(HttpServletRequest httpServletRequest) { - return httpServletRequest.getUserPrincipal(); - } - - @Nullable - @Override - public Object getAttribute(ServletRequest servletRequest, String attributeName) { - return servletRequest.getAttribute(attributeName); - } - - @Nullable - @Override - public Object getHttpAttribute(HttpServletRequest httpServletRequest, String attributeName) { - return httpServletRequest.getAttribute(attributeName); - } - @Override - public Collection getHeaderNames(HttpServletResponse httpServletResponse) { - return httpServletResponse.getHeaderNames(); - } - - @Override - public Collection getHeaders(HttpServletResponse httpServletResponse, String headerName) { - return httpServletResponse.getHeaders(headerName); - } - - @Override - public Map getParameterMap(HttpServletRequest httpServletRequest) { - return httpServletRequest.getParameterMap(); - } - - @Override - public boolean isCommitted(HttpServletResponse httpServletResponse) { - return httpServletResponse.isCommitted(); - } - - @Override - public int getStatus(HttpServletResponse httpServletResponse) { - return httpServletResponse.getStatus(); - } - - @Override - public ServletContext getServletContext(ServletRequest servletRequest) { - return servletRequest.getServletContext(); + onExitServlet(adapter, servletRequest, servletResponse, transactionOrScopeOrSpan, t, thiz); } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JavaxServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JavaxServletApiAdvice.java index 114b73512b..7841f11eeb 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JavaxServletApiAdvice.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/JavaxServletApiAdvice.java @@ -18,41 +18,21 @@ */ package co.elastic.apm.agent.servlet; -import co.elastic.apm.agent.impl.GlobalTracer; -import co.elastic.apm.agent.impl.context.Request; -import co.elastic.apm.agent.impl.transaction.Transaction; -import co.elastic.apm.agent.servlet.helper.JavaxServletTransactionCreationHelper; +import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter; import net.bytebuddy.asm.Advice; import javax.annotation.Nullable; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.security.Principal; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Map; +public class JavaxServletApiAdvice extends ServletApiAdvice { -public class JavaxServletApiAdvice extends ServletApiAdvice implements ServletHelper { - - private static JavaxServletTransactionCreationHelper transactionCreationHelper; - private static JavaxServletApiAdvice helper; - static { - transactionCreationHelper = new JavaxServletTransactionCreationHelper(GlobalTracer.requireTracerImpl()); - helper = new JavaxServletApiAdvice(); - } + private static final JavaxServletApiAdapter adapter = JavaxServletApiAdapter.get(); @Nullable @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object onEnterServletService(@Advice.Argument(0) ServletRequest servletRequest) { - return onServletEnter(servletRequest, helper); + return onServletEnter(adapter, servletRequest); } @@ -62,202 +42,6 @@ public static void onExitServletService(@Advice.Argument(0) ServletRequest servl @Advice.Enter @Nullable Object transactionOrScopeOrSpan, @Advice.Thrown @Nullable Throwable t, @Advice.This Object thiz) { - onExitServlet(servletRequest, servletResponse, transactionOrScopeOrSpan, t, thiz, helper); - } - - @Override - public boolean isHttpServletRequest(ServletRequest servletRequest) { - return servletRequest instanceof HttpServletRequest; - } - - @Override - public boolean isHttpServletResponse(ServletResponse servletResponse) { - return servletResponse instanceof HttpServletResponse; - } - - @Override - public boolean isRequestDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.REQUEST; - } - - @Override - public boolean isAsyncDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.ASYNC; - } - - @Override - public boolean isForwardDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.FORWARD; - } - - @Override - public boolean isIncludeDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.INCLUDE; - } - - @Override - public boolean isErrorDispatcherType(ServletRequest servletRequest) { - return servletRequest.getDispatcherType() == DispatcherType.ERROR; - } - - @Override - public ClassLoader getClassloader(ServletContext servletContext) { - return transactionCreationHelper.getClassloader(servletContext); - } - - @Override - public String getServletContextName(ServletContext servletContext) { - return servletContext.getServletContextName(); - } - - @Override - public String getContextPath(ServletContext servletContext) { - return servletContext.getContextPath(); - } - - @Override - public Transaction createAndActivateTransaction(HttpServletRequest httpServletRequest) { - return transactionCreationHelper.createAndActivateTransaction(httpServletRequest); - } - - @Override - public void handleCookies(Request request, HttpServletRequest servletRequest) { - if (servletRequest.getCookies() != null) { - for (Cookie cookie : servletRequest.getCookies()) { - request.addCookie(cookie.getName(), cookie.getValue()); - } - } - } - - @Override - public Enumeration getRequestHeaderNames(HttpServletRequest servletRequest) { - return servletRequest.getHeaderNames(); - } - - @Override - public Enumeration getRequestHeaders(HttpServletRequest servletRequest, String name) { - return servletRequest.getHeaders(name); - } - - @Override - public String getHeader(HttpServletRequest httpServletRequest, String name) { - return httpServletRequest.getHeader(name); - } - - @Override - public String getProtocol(HttpServletRequest servletRequest) { - return servletRequest.getProtocol(); - } - - @Override - public String getMethod(HttpServletRequest servletRequest) { - return servletRequest.getMethod(); - } - - @Override - public boolean isSecure(HttpServletRequest servletRequest) { - return servletRequest.isSecure(); - } - - @Override - public String getScheme(HttpServletRequest servletRequest) { - return servletRequest.getScheme(); - } - - @Override - public String getServerName(HttpServletRequest servletRequest) { - return servletRequest.getServerName(); - } - - @Override - public int getServerPort(HttpServletRequest servletRequest) { - return servletRequest.getServerPort(); - } - - @Override - public String getRequestURI(HttpServletRequest servletRequest) { - return servletRequest.getRequestURI(); - } - - @Override - public String getQueryString(HttpServletRequest servletRequest) { - return servletRequest.getQueryString(); - } - - @Override - public String getRemoteAddr(HttpServletRequest servletRequest) { - return servletRequest.getRemoteAddr(); - } - - @Override - public String getServletPath(HttpServletRequest servletRequest) { - return servletRequest.getServletPath(); - } - - @Override - public String getPathInfo(HttpServletRequest servletRequest) { - return servletRequest.getPathInfo(); - } - - @Override - public Object getIncludeServletPathAttribute(HttpServletRequest servletRequest) { - return servletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); - } - - @Override - public Object getIncludePathInfoAttribute(HttpServletRequest servletRequest) { - return servletRequest.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); - } - - @Override - public boolean isInstanceOfHttpServlet(Object object) { - return object instanceof HttpServlet; - } - - @Override - public Principal getUserPrincipal(HttpServletRequest httpServletRequest) { - return httpServletRequest.getUserPrincipal(); - } - - @Nullable - @Override - public Object getAttribute(ServletRequest servletRequest, String attributeName) { - return servletRequest.getAttribute(attributeName); - } - - @Nullable - @Override - public Object getHttpAttribute(HttpServletRequest httpServletRequest, String attributeName) { - return httpServletRequest.getAttribute(attributeName); - } - - @Override - public Collection getHeaderNames(HttpServletResponse httpServletResponse) { - return httpServletResponse.getHeaderNames(); - } - - @Override - public Collection getHeaders(HttpServletResponse httpServletResponse, String headerName) { - return httpServletResponse.getHeaders(headerName); - } - - @Override - public Map getParameterMap(HttpServletRequest httpServletRequest) { - return httpServletRequest.getParameterMap(); - } - - @Override - public boolean isCommitted(HttpServletResponse httpServletResponse) { - return httpServletResponse.isCommitted(); - } - - @Override - public int getStatus(HttpServletResponse httpServletResponse) { - return httpServletResponse.getStatus(); - } - - @Override - public ServletContext getServletContext(ServletRequest servletRequest) { - return servletRequest.getServletContext(); + onExitServlet(adapter, servletRequest, servletResponse, transactionOrScopeOrSpan, t, thiz); } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java index b9ad3b2eab..74d76223a3 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java @@ -31,6 +31,7 @@ import co.elastic.apm.agent.sdk.state.GlobalVariables; import co.elastic.apm.agent.sdk.weakconcurrent.DetachedThreadLocal; import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; +import co.elastic.apm.agent.servlet.adapter.ServletApiAdapter; import co.elastic.apm.agent.util.TransactionNameUtils; import javax.annotation.Nullable; @@ -42,18 +43,13 @@ import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_LOW_LEVEL_FRAMEWORK; import static co.elastic.apm.agent.servlet.ServletTransactionHelper.TRANSACTION_ATTRIBUTE; -import static co.elastic.apm.agent.servlet.ServletTransactionHelper.determineServiceName; public abstract class ServletApiAdvice { private static final String FRAMEWORK_NAME = "Servlet API"; static final String SPAN_TYPE = "servlet"; static final String SPAN_SUBTYPE = "request-dispatcher"; - private static final ServletTransactionHelper servletTransactionHelper; - - static { - servletTransactionHelper = new ServletTransactionHelper(GlobalTracer.requireTracerImpl()); - } + private static final ServletTransactionHelper servletTransactionHelper = new ServletTransactionHelper(GlobalTracer.requireTracerImpl()); private static final DetachedThreadLocal excluded = GlobalVariables.get(ServletApiAdvice.class, "excluded", WeakConcurrent.buildThreadLocal()); private static final DetachedThreadLocal servletPathTL = GlobalVariables.get(ServletApiAdvice.class, "servletPath", WeakConcurrent.buildThreadLocal()); @@ -62,38 +58,40 @@ public abstract class ServletApiAdvice { private static final List requestExceptionAttributes = Arrays.asList("javax.servlet.error.exception", "jakarta.servlet.error.exception", "exception", "org.springframework.web.servlet.DispatcherServlet.EXCEPTION", "co.elastic.apm.exception"); @Nullable - public static Object onServletEnter(REQUEST servletRequest, ServletHelper helper) { + public static Object onServletEnter( + ServletApiAdapter adapter, + Object servletRequest) { + ElasticApmTracer tracer = GlobalTracer.getTracerImpl(); if (tracer == null) { return null; } + + final HttpServletRequest httpServletRequest = adapter.asHttpServletRequest(servletRequest); + if (httpServletRequest == null) { + return null; + } AbstractSpan ret = null; // re-activate transactions for async requests - final Transaction transactionAttr = (Transaction) helper.getAttribute(servletRequest, TRANSACTION_ATTRIBUTE); + final Transaction transactionAttr = (Transaction) adapter.getAttribute(httpServletRequest, TRANSACTION_ATTRIBUTE); if (tracer.currentTransaction() == null && transactionAttr != null) { return transactionAttr.activateInScope(); } - if (!tracer.isRunning() || !helper.isHttpServletRequest(servletRequest)) { + if (!tracer.isRunning()) { return null; } - final HTTPREQUEST httpServletRequest = (HTTPREQUEST) servletRequest; CoreConfiguration coreConfig = tracer.getConfig(CoreConfiguration.class); - if (helper.isRequestDispatcherType(servletRequest)) { + if (adapter.isRequestDispatcherType(httpServletRequest)) { if (Boolean.TRUE == excluded.get()) { return null; } - CONTEXT servletContext = helper.getServletContext(servletRequest); - if (servletContext != null) { - ClassLoader servletCL = helper.getClassloader(servletContext); - // this makes sure service name discovery also works when attaching at runtime - determineServiceName(helper.getServletContextName(servletContext), servletCL, helper.getContextPath(servletContext)); - } + ServletServiceNameHelper.determineServiceName(adapter, adapter.getServletContext(httpServletRequest), tracer); - Transaction transaction = helper.createAndActivateTransaction(httpServletRequest); + Transaction transaction = servletTransactionHelper.createAndActivateTransaction(adapter, adapter, httpServletRequest); if (transaction == null) { // if the httpServletRequest is excluded, avoid matching all exclude patterns again on each filter invocation @@ -103,40 +101,40 @@ public static Object onS final Request req = transaction.getContext().getRequest(); if (transaction.isSampled() && coreConfig.isCaptureHeaders()) { - helper.handleCookies(req, httpServletRequest); + adapter.handleCookies(req, httpServletRequest); - final Enumeration headerNames = helper.getRequestHeaderNames(httpServletRequest); + final Enumeration headerNames = adapter.getRequestHeaderNames(httpServletRequest); if (headerNames != null) { while (headerNames.hasMoreElements()) { final String headerName = headerNames.nextElement(); - req.addHeader(headerName, helper.getRequestHeaders(httpServletRequest, headerName)); + req.addHeader(headerName, adapter.getRequestHeaders(httpServletRequest, headerName)); } } } transaction.setFrameworkName(FRAMEWORK_NAME); - servletTransactionHelper.fillRequestContext(transaction, helper.getProtocol(httpServletRequest), helper.getMethod(httpServletRequest), helper.isSecure(httpServletRequest), - helper.getScheme(httpServletRequest), helper.getServerName(httpServletRequest), helper.getServerPort(httpServletRequest), helper.getRequestURI(httpServletRequest), helper.getQueryString(httpServletRequest), - helper.getRemoteAddr(httpServletRequest), helper.getHeader(httpServletRequest, "Content-Type")); + servletTransactionHelper.fillRequestContext(transaction, adapter.getProtocol(httpServletRequest), adapter.getMethod(httpServletRequest), adapter.isSecure(httpServletRequest), + adapter.getScheme(httpServletRequest), adapter.getServerName(httpServletRequest), adapter.getServerPort(httpServletRequest), adapter.getRequestURI(httpServletRequest), adapter.getQueryString(httpServletRequest), + adapter.getRemoteAddr(httpServletRequest), adapter.getHeader(httpServletRequest, "Content-Type")); ret = transaction; - } else if (!helper.isAsyncDispatcherType(servletRequest) && coreConfig.isInstrumentationEnabled(Constants.SERVLET_API_DISPATCH)) { + } else if (!adapter.isAsyncDispatcherType(httpServletRequest) && coreConfig.isInstrumentationEnabled(Constants.SERVLET_API_DISPATCH)) { final AbstractSpan parent = tracer.getActive(); if (parent != null) { Object servletPath = null; Object pathInfo = null; RequestDispatcherSpanType spanType = null; - if (helper.isForwardDispatcherType(servletRequest)) { + if (adapter.isForwardDispatcherType(httpServletRequest)) { spanType = RequestDispatcherSpanType.FORWARD; - servletPath = helper.getServletPath(httpServletRequest); - pathInfo = helper.getPathInfo(httpServletRequest); - } else if (helper.isIncludeDispatcherType(servletRequest)) { + servletPath = adapter.getServletPath(httpServletRequest); + pathInfo = adapter.getPathInfo(httpServletRequest); + } else if (adapter.isIncludeDispatcherType(httpServletRequest)) { spanType = RequestDispatcherSpanType.INCLUDE; - servletPath = helper.getIncludeServletPathAttribute(httpServletRequest); - pathInfo = helper.getIncludePathInfoAttribute(httpServletRequest); - } else if (helper.isErrorDispatcherType(servletRequest)) { + servletPath = adapter.getIncludeServletPathAttribute(httpServletRequest); + pathInfo = adapter.getIncludePathInfoAttribute(httpServletRequest); + } else if (adapter.isErrorDispatcherType(httpServletRequest)) { spanType = RequestDispatcherSpanType.ERROR; - servletPath = helper.getServletPath(httpServletRequest); + servletPath = adapter.getServletPath(httpServletRequest); } if (spanType != null && (areNotEqual(servletPathTL.get(), servletPath) || areNotEqual(pathInfoTL.get(), pathInfo))) { @@ -161,12 +159,14 @@ public static Object onS return ret; } - public static void onExitServlet(REQUEST servletRequest, - RESPONSE servletResponse, - @Nullable Object transactionOrScopeOrSpan, - @Nullable Throwable t, - Object thiz, - ServletHelper helper) { + public static void onExitServlet( + ServletApiAdapter adapter, + Object servletRequest, + Object servletResponse, + @Nullable Object transactionOrScopeOrSpan, + @Nullable Throwable t, + Object thiz) { + ElasticApmTracer tracer = GlobalTracer.getTracerImpl(); if (tracer == null) { return; @@ -186,39 +186,38 @@ public static void onExi if (scope != null) { scope.close(); } - if (helper.isInstanceOfHttpServlet(thiz) && helper.isHttpServletRequest(servletRequest)) { + HttpServletRequest httpServletRequest = adapter.asHttpServletRequest(servletRequest); + HttpServletResponse httpServletResponse = adapter.asHttpServletResponse(servletResponse); + if (adapter.isInstanceOfHttpServlet(thiz) && httpServletRequest != null) { Transaction currentTransaction = tracer.currentTransaction(); if (currentTransaction != null) { - final HTTPREQUEST httpServletRequest = (HTTPREQUEST) servletRequest; - TransactionNameUtils.setTransactionNameByServletClass(helper.getMethod(httpServletRequest), thiz.getClass(), currentTransaction.getAndOverrideName(PRIO_LOW_LEVEL_FRAMEWORK)); - final Principal userPrincipal = helper.getUserPrincipal(httpServletRequest); + TransactionNameUtils.setTransactionNameByServletClass(adapter.getMethod(httpServletRequest), thiz.getClass(), currentTransaction.getAndOverrideName(PRIO_LOW_LEVEL_FRAMEWORK)); + final Principal userPrincipal = adapter.getUserPrincipal(httpServletRequest); ServletTransactionHelper.setUsernameIfUnset(userPrincipal != null ? userPrincipal.getName() : null, currentTransaction.getContext()); } } if (transaction != null && - helper.isHttpServletRequest(servletRequest) && - helper.isHttpServletResponse(servletResponse)) { + httpServletRequest != null && + httpServletResponse != null) { - final HTTPREQUEST httpServletRequest = (HTTPREQUEST) servletRequest; - if (helper.getHttpAttribute(httpServletRequest, ServletTransactionHelper.ASYNC_ATTRIBUTE) != null) { + if (adapter.getHttpAttribute(httpServletRequest, ServletTransactionHelper.ASYNC_ATTRIBUTE) != null) { // HttpServletRequest.startAsync was invoked on this httpServletRequest. // The transaction should be handled from now on by the other thread committing the response transaction.deactivate(); } else { // this is not an async httpServletRequest, so we can end the transaction immediately - final HTTPRESPONSE response = (HTTPRESPONSE) servletResponse; if (transaction.isSampled() && tracer.getConfig(CoreConfiguration.class).isCaptureHeaders()) { final Response resp = transaction.getContext().getResponse(); - for (String headerName : helper.getHeaderNames(response)) { - resp.addHeader(headerName, helper.getHeaders(response, headerName)); + for (String headerName : adapter.getHeaderNames(httpServletResponse)) { + resp.addHeader(headerName, adapter.getHeaders(httpServletResponse, headerName)); } } // httpServletRequest.getParameterMap() may allocate a new map, depending on the servlet container implementation // so only call this method if necessary - final String contentTypeHeader = helper.getHeader(httpServletRequest, "Content-Type"); + final String contentTypeHeader = adapter.getHeader(httpServletRequest, "Content-Type"); final Map parameterMap; - if (transaction.isSampled() && servletTransactionHelper.captureParameters(helper.getMethod(httpServletRequest), contentTypeHeader)) { - parameterMap = helper.getParameterMap(httpServletRequest); + if (transaction.isSampled() && servletTransactionHelper.captureParameters(adapter.getMethod(httpServletRequest), contentTypeHeader)) { + parameterMap = adapter.getParameterMap(httpServletRequest); } else { parameterMap = null; } @@ -229,7 +228,7 @@ public static void onExi final int size = requestExceptionAttributes.size(); for (int i = 0; i < size; i++) { String attributeName = requestExceptionAttributes.get(i); - Object throwable = helper.getHttpAttribute(httpServletRequest, attributeName); + Object throwable = adapter.getHttpAttribute(httpServletRequest, attributeName); if (throwable instanceof Throwable) { t2 = (Throwable) throwable; if (!attributeName.equals("javax.servlet.error.exception") && !attributeName.equals("jakarta.servlet.error.exception")) { @@ -240,9 +239,9 @@ public static void onExi } } - servletTransactionHelper.onAfter(transaction, t == null ? t2 : t, helper.isCommitted(response), helper.getStatus(response), - overrideStatusCodeOnThrowable, helper.getMethod(httpServletRequest), parameterMap, helper.getServletPath(httpServletRequest), - helper.getPathInfo(httpServletRequest), contentTypeHeader, true + servletTransactionHelper.onAfter(transaction, t == null ? t2 : t, adapter.isCommitted(httpServletResponse), adapter.getStatus(httpServletResponse), + overrideStatusCodeOnThrowable, adapter.getMethod(httpServletRequest), parameterMap, adapter.getServletPath(httpServletRequest), + adapter.getPathInfo(httpServletRequest), contentTypeHeader, true ); } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java new file mode 100644 index 0000000000..8ed2c4850d --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java @@ -0,0 +1,103 @@ +/* + * 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.servlet; + +import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.impl.Tracer; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.sdk.state.GlobalState; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; +import co.elastic.apm.agent.servlet.adapter.ServletContextAdapter; + +import javax.annotation.Nullable; +import java.io.InputStream; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +@GlobalState +public class ServletServiceNameHelper { + + public static final WeakMap nameInitialized = WeakConcurrent.buildMap(); + private static final Logger logger = LoggerFactory.getLogger(ServletServiceNameHelper.class); + + // this makes sure service name discovery also works when attaching at runtime + public static void determineServiceName(ServletContextAdapter adapter, + @Nullable ServletContext servletContext, + Tracer tracer) { + + if (servletContext == null) { + return; + } + ClassLoader servletContextClassLoader = adapter.getClassLoader(servletContext); + if (servletContextClassLoader == null || nameInitialized.putIfAbsent(servletContextClassLoader, Boolean.TRUE) != null) { + return; + } + ServiceInfo serviceInfo = detectServiceInfo(adapter, servletContext, servletContextClassLoader); + tracer.overrideServiceInfoForClassLoader(servletContextClassLoader, serviceInfo); + } + + public static ServiceInfo detectServiceInfo(ServletContextAdapter adapter, ServletContext servletContext, ClassLoader servletContextClassLoader) { + String servletContextName = adapter.getServletContextName(servletContext); + String contextPath = adapter.getContextPath(servletContext); + + if (logger.isDebugEnabled()) { + logger.debug("Inferring service name for class loader [{}] based on servlet context path `{}` and request context path `{}`", + servletContextClassLoader, + servletContextName, + contextPath + ); + } + + ServiceInfo fromWarManifest = ServiceInfo.fromManifest(getManifest(adapter, servletContext)); + ServiceInfo fromContextName = ServiceInfo.empty(); + if (!"application".equals(servletContextName) && !"".equals(servletContextName) && !"/".equals(servletContextName)) { + // payara returns an empty string as opposed to null + // spring applications which did not set spring.application.name have application as the default + // jetty returns context path when no display name is set, which could be the root context of "/" + // this is a worse default than the one we would otherwise choose + fromContextName = ServiceInfo.of(servletContextName); + } + ServiceInfo fromContextPath = ServiceInfo.empty(); + if (contextPath != null && !contextPath.isEmpty()) { + // remove leading slash + fromContextPath = ServiceInfo.of(contextPath.substring(1)); + } + return fromWarManifest.withFallback(fromContextName).withFallback(fromContextPath); + } + + @Nullable + private static Manifest getManifest(ServletContextAdapter adapter, ServletContext servletContext) { + + try (InputStream manifestStream = adapter.getResourceAsStream(servletContext, "/" + JarFile.MANIFEST_NAME)) { + if (manifestStream == null) { + return null; + } + return new Manifest(manifestStream); + } catch (Exception e) { + return null; + } + } + + // visible for testing as clearing cache is required between tests execution + static void clearServiceNameCache() { + nameInitialized.clear(); + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java index f32fa2ccc5..3b6d3da873 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java @@ -20,7 +20,6 @@ import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.impl.GlobalTracer; import co.elastic.apm.agent.impl.context.Request; import co.elastic.apm.agent.impl.context.Response; import co.elastic.apm.agent.impl.context.TransactionContext; @@ -28,9 +27,11 @@ import co.elastic.apm.agent.impl.context.web.WebConfiguration; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; -import co.elastic.apm.agent.util.TransactionNameUtils; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.servlet.adapter.ServletContextAdapter; +import co.elastic.apm.agent.servlet.adapter.ServletRequestAdapter; +import co.elastic.apm.agent.util.TransactionNameUtils; import javax.annotation.Nullable; import java.util.Arrays; @@ -41,7 +42,6 @@ import static co.elastic.apm.agent.configuration.CoreConfiguration.EventType.OFF; import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_DEFAULT; import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_LOW_LEVEL_FRAMEWORK; -import static co.elastic.apm.agent.servlet.ServletGlobalState.nameInitialized; public class ServletTransactionHelper { @@ -57,41 +57,49 @@ public class ServletTransactionHelper { private final Set METHODS_WITH_BODY = new HashSet<>(Arrays.asList("POST", "PUT", "PATCH", "DELETE")); private final CoreConfiguration coreConfiguration; private final WebConfiguration webConfiguration; + private final ElasticApmTracer tracer; public ServletTransactionHelper(ElasticApmTracer tracer) { this.coreConfiguration = tracer.getConfig(CoreConfiguration.class); this.webConfiguration = tracer.getConfig(WebConfiguration.class); + this.tracer = tracer; } - public static void determineServiceName(@Nullable String servletContextName, @Nullable ClassLoader servletContextClassLoader, @Nullable String contextPath) { - if (servletContextClassLoader == null || nameInitialized.putIfAbsent(servletContextClassLoader, Boolean.TRUE) != null) { - return; + @Nullable + public Transaction createAndActivateTransaction( + ServletRequestAdapter requestAdapter, + ServletContextAdapter contextAdapter, + HttpServletRequest request) { + // only create a transaction if there is not already one + if (tracer.currentTransaction() != null) { + return null; } - - if (logger.isDebugEnabled()) { - logger.debug("Inferring service name for class loader [{}] based on servlet context path `{}` and request context path `{}`", - servletContextClassLoader, - servletContextName, - contextPath - ); + if (isExcluded(requestAdapter.getHeader(request, "User-Agent"), requestAdapter.getRequestURI(request))) { + return null; } + ClassLoader cl = contextAdapter.getClassLoader(requestAdapter.getServletContext(request)); + Transaction transaction = tracer.startChildTransaction(request, requestAdapter.getRequestHeaderGetter(), cl); + if (transaction != null) { + transaction.activate(); + } + return transaction; + } + + public boolean isExcluded(@Nullable String userAgent, String requestUri) { + final WildcardMatcher excludeUrlMatcher = WildcardMatcher.anyMatch(webConfiguration.getIgnoreUrls(), requestUri); - @Nullable - String serviceName = servletContextName; - if ("application".equals(serviceName) || "".equals(serviceName) || "/".equals(serviceName)) { - // payara returns an empty string as opposed to null - // spring applications which did not set spring.application.name have application as the default - // jetty returns context path when no display name is set, which could be the root context of "/" - // this is a worse default than the one we would otherwise choose - serviceName = null; + if (excludeUrlMatcher != null && logger.isDebugEnabled()) { + logger.debug("Not tracing this request as the URL {} is ignored by the matcher {}", requestUri, excludeUrlMatcher); } - if (serviceName == null && contextPath != null && !contextPath.isEmpty()) { - // remove leading slash - serviceName = contextPath.substring(1); + final WildcardMatcher excludeAgentMatcher = userAgent != null ? WildcardMatcher.anyMatch(webConfiguration.getIgnoreUserAgents(), userAgent) : null; + if (excludeAgentMatcher != null) { + logger.debug("Not tracing this request as the User-Agent {} is ignored by the matcher {}", userAgent, excludeAgentMatcher); } - if (serviceName != null) { - GlobalTracer.get().overrideServiceNameForClassLoader(servletContextClassLoader, serviceName); + boolean isExcluded = excludeUrlMatcher != null || excludeAgentMatcher != null; + if (!isExcluded && logger.isTraceEnabled()) { + logger.trace("No matcher found for excluding this request with URL: {}, and User-Agent: {}", requestUri, userAgent); } + return isExcluded; } public void fillRequestContext(Transaction transaction, String protocol, String method, boolean secure, diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/FilterAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/FilterAdapter.java new file mode 100644 index 0000000000..e65ff90367 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/FilterAdapter.java @@ -0,0 +1,26 @@ +/* + * 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.servlet.adapter; + +import co.elastic.apm.agent.sdk.state.GlobalState; + +@GlobalState +public interface FilterAdapter { + ServletContext getServletContextFromFilterConfig(FilterConfig filterConfig); +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JakartaServletApiAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JakartaServletApiAdapter.java new file mode 100644 index 0000000000..c2e8369675 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JakartaServletApiAdapter.java @@ -0,0 +1,289 @@ +/* + * 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.servlet.adapter; + +import co.elastic.apm.agent.impl.context.Request; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.servlet.helper.JakartaServletRequestHeaderGetter; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import javax.annotation.Nullable; +import java.io.InputStream; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Map; + +public class JakartaServletApiAdapter implements ServletApiAdapter { + + public static final JakartaServletApiAdapter INSTANCE = new JakartaServletApiAdapter(); + + private JakartaServletApiAdapter() { + } + + public static JakartaServletApiAdapter get() { + return INSTANCE; + } + + @Nullable + @Override + public HttpServletRequest asHttpServletRequest(Object servletRequest) { + if (servletRequest instanceof HttpServletRequest) { + return (HttpServletRequest) servletRequest; + } + return null; + } + + @Nullable + @Override + public HttpServletResponse asHttpServletResponse(Object servletResponse) { + if (servletResponse instanceof HttpServletResponse) { + return (HttpServletResponse) servletResponse; + } + return null; + } + + @Override + public boolean isRequestDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.REQUEST; + } + + @Override + public boolean isAsyncDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.ASYNC; + } + + @Override + public boolean isForwardDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.FORWARD; + } + + @Override + public boolean isIncludeDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.INCLUDE; + } + + @Override + public boolean isErrorDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.ERROR; + } + + @Nullable + @Override + public ClassLoader getClassLoader(@Nullable ServletContext servletContext) { + if (servletContext == null) { + return null; + } + + // getClassloader might throw UnsupportedOperationException + // see Section 4.4 of the Servlet 3.0 specification + try { + return servletContext.getClassLoader(); + } catch (UnsupportedOperationException ignore) { + // silently ignored + return null; + } + } + + @Override + public String getServletContextName(ServletContext servletContext) { + return servletContext.getServletContextName(); + } + + @Nullable + @Override + public String getContextPath(ServletContext servletContext) { + return servletContext.getContextPath(); + } + + @Override + public void handleCookies(Request request, HttpServletRequest servletRequest) { + if (servletRequest.getCookies() != null) { + for (Cookie cookie : servletRequest.getCookies()) { + request.addCookie(cookie.getName(), cookie.getValue()); + } + } + } + + @Override + public Enumeration getRequestHeaderNames(HttpServletRequest servletRequest) { + return servletRequest.getHeaderNames(); + } + + @Override + public Enumeration getRequestHeaders(HttpServletRequest servletRequest, String name) { + return servletRequest.getHeaders(name); + } + + @Override + public String getHeader(HttpServletRequest httpServletRequest, String name) { + return httpServletRequest.getHeader(name); + } + + @Override + public String getProtocol(HttpServletRequest servletRequest) { + return servletRequest.getProtocol(); + } + + @Override + public String getMethod(HttpServletRequest servletRequest) { + return servletRequest.getMethod(); + } + + @Override + public boolean isSecure(HttpServletRequest servletRequest) { + return servletRequest.isSecure(); + } + + @Override + public String getScheme(HttpServletRequest servletRequest) { + return servletRequest.getScheme(); + } + + @Override + public String getServerName(HttpServletRequest servletRequest) { + return servletRequest.getServerName(); + } + + @Override + public int getServerPort(HttpServletRequest servletRequest) { + return servletRequest.getServerPort(); + } + + @Override + public String getRequestURI(HttpServletRequest servletRequest) { + return servletRequest.getRequestURI(); + } + + @Override + public String getQueryString(HttpServletRequest servletRequest) { + return servletRequest.getQueryString(); + } + + @Override + public String getRemoteAddr(HttpServletRequest servletRequest) { + return servletRequest.getRemoteAddr(); + } + + @Override + public String getServletPath(HttpServletRequest servletRequest) { + return servletRequest.getServletPath(); + } + + @Override + public String getPathInfo(HttpServletRequest servletRequest) { + return servletRequest.getPathInfo(); + } + + @Override + public Object getIncludeServletPathAttribute(HttpServletRequest servletRequest) { + return servletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + } + + @Override + public Object getIncludePathInfoAttribute(HttpServletRequest servletRequest) { + return servletRequest.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + } + + @Override + public boolean isInstanceOfHttpServlet(Object object) { + return object instanceof HttpServlet; + } + + @Override + public ServletContext getServletContextFromServletContextEvent(ServletContextEvent servletContextEvent) { + return servletContextEvent.getServletContext(); + } + + @Override + public ServletContext getServletContextFromServletConfig(ServletConfig filterConfig) { + return filterConfig.getServletContext(); + } + + @Override + public Principal getUserPrincipal(HttpServletRequest httpServletRequest) { + return httpServletRequest.getUserPrincipal(); + } + + @Nullable + @Override + public Object getAttribute(HttpServletRequest servletRequest, String attributeName) { + return servletRequest.getAttribute(attributeName); + } + + @Nullable + @Override + public Object getHttpAttribute(HttpServletRequest httpServletRequest, String attributeName) { + return httpServletRequest.getAttribute(attributeName); + } + @Override + public Collection getHeaderNames(HttpServletResponse httpServletResponse) { + return httpServletResponse.getHeaderNames(); + } + + @Override + public Collection getHeaders(HttpServletResponse httpServletResponse, String headerName) { + return httpServletResponse.getHeaders(headerName); + } + + @Override + public Map getParameterMap(HttpServletRequest httpServletRequest) { + return httpServletRequest.getParameterMap(); + } + + @Override + public boolean isCommitted(HttpServletResponse httpServletResponse) { + return httpServletResponse.isCommitted(); + } + + @Override + public int getStatus(HttpServletResponse httpServletResponse) { + return httpServletResponse.getStatus(); + } + + @Override + public ServletContext getServletContext(HttpServletRequest servletRequest) { + return servletRequest.getServletContext(); + } + + @Nullable + @Override + public InputStream getResourceAsStream(ServletContext servletContext, String path) { + return servletContext.getResourceAsStream(path); + } + + @Override + public TextHeaderGetter getRequestHeaderGetter() { + return JakartaServletRequestHeaderGetter.getInstance(); + } + + @Override + public ServletContext getServletContextFromFilterConfig(FilterConfig filterConfig) { + return filterConfig.getServletContext(); + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JavaxServletApiAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JavaxServletApiAdapter.java new file mode 100644 index 0000000000..877e267683 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JavaxServletApiAdapter.java @@ -0,0 +1,289 @@ +/* + * 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.servlet.adapter; + +import co.elastic.apm.agent.impl.context.Request; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.servlet.helper.JavaxServletRequestHeaderGetter; + +import javax.annotation.Nullable; +import javax.servlet.DispatcherType; +import javax.servlet.FilterConfig; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Map; + +public class JavaxServletApiAdapter implements ServletApiAdapter { + + private static final JavaxServletApiAdapter INSTANCE = new JavaxServletApiAdapter(); + + private JavaxServletApiAdapter() { + } + + public static JavaxServletApiAdapter get() { + return INSTANCE; + } + + @Override + public HttpServletRequest asHttpServletRequest(Object servletRequest) { + if (servletRequest instanceof HttpServletRequest) { + return (HttpServletRequest) servletRequest; + } + return null; + } + + @Nullable + @Override + public HttpServletResponse asHttpServletResponse(Object servletResponse) { + if (servletResponse instanceof HttpServletResponse) { + return (HttpServletResponse) servletResponse; + } + return null; + } + + @Override + public boolean isRequestDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.REQUEST; + } + + @Override + public boolean isAsyncDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.ASYNC; + } + + @Override + public boolean isForwardDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.FORWARD; + } + + @Override + public boolean isIncludeDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.INCLUDE; + } + + @Override + public boolean isErrorDispatcherType(HttpServletRequest servletRequest) { + return servletRequest.getDispatcherType() == DispatcherType.ERROR; + } + + @Nullable + @Override + public ClassLoader getClassLoader(@Nullable ServletContext servletContext) { + if (servletContext == null) { + return null; + } + + // getClassloader might throw UnsupportedOperationException + // see Section 4.4 of the Servlet 3.0 specification + try { + return servletContext.getClassLoader(); + } catch (UnsupportedOperationException ignore) { + // silently ignored + return null; + } + } + + @Override + public String getServletContextName(ServletContext servletContext) { + return servletContext.getServletContextName(); + } + + @Nullable + @Override + public String getContextPath(ServletContext servletContext) { + return servletContext.getContextPath(); + } + + @Override + public void handleCookies(Request request, HttpServletRequest servletRequest) { + if (servletRequest.getCookies() != null) { + for (Cookie cookie : servletRequest.getCookies()) { + request.addCookie(cookie.getName(), cookie.getValue()); + } + } + } + + @Override + public Enumeration getRequestHeaderNames(HttpServletRequest servletRequest) { + return servletRequest.getHeaderNames(); + } + + @Override + public Enumeration getRequestHeaders(HttpServletRequest servletRequest, String name) { + return servletRequest.getHeaders(name); + } + + @Override + public String getHeader(HttpServletRequest httpServletRequest, String name) { + return httpServletRequest.getHeader(name); + } + + @Override + public String getProtocol(HttpServletRequest servletRequest) { + return servletRequest.getProtocol(); + } + + @Override + public String getMethod(HttpServletRequest servletRequest) { + return servletRequest.getMethod(); + } + + @Override + public boolean isSecure(HttpServletRequest servletRequest) { + return servletRequest.isSecure(); + } + + @Override + public String getScheme(HttpServletRequest servletRequest) { + return servletRequest.getScheme(); + } + + @Override + public String getServerName(HttpServletRequest servletRequest) { + return servletRequest.getServerName(); + } + + @Override + public int getServerPort(HttpServletRequest servletRequest) { + return servletRequest.getServerPort(); + } + + @Override + public String getRequestURI(HttpServletRequest servletRequest) { + return servletRequest.getRequestURI(); + } + + @Override + public String getQueryString(HttpServletRequest servletRequest) { + return servletRequest.getQueryString(); + } + + @Override + public String getRemoteAddr(HttpServletRequest servletRequest) { + return servletRequest.getRemoteAddr(); + } + + @Override + public String getServletPath(HttpServletRequest servletRequest) { + return servletRequest.getServletPath(); + } + + @Override + public String getPathInfo(HttpServletRequest servletRequest) { + return servletRequest.getPathInfo(); + } + + @Override + public Object getIncludeServletPathAttribute(HttpServletRequest servletRequest) { + return servletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + } + + @Override + public Object getIncludePathInfoAttribute(HttpServletRequest servletRequest) { + return servletRequest.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + } + + @Override + public boolean isInstanceOfHttpServlet(Object object) { + return object instanceof HttpServlet; + } + + @Override + public ServletContext getServletContextFromServletContextEvent(ServletContextEvent servletContextEvent) { + return servletContextEvent.getServletContext(); + } + + @Override + public ServletContext getServletContextFromServletConfig(ServletConfig filterConfig) { + return filterConfig.getServletContext(); + } + + @Override + public Principal getUserPrincipal(HttpServletRequest httpServletRequest) { + return httpServletRequest.getUserPrincipal(); + } + + @Nullable + @Override + public Object getAttribute(HttpServletRequest servletRequest, String attributeName) { + return servletRequest.getAttribute(attributeName); + } + + @Nullable + @Override + public Object getHttpAttribute(HttpServletRequest httpServletRequest, String attributeName) { + return httpServletRequest.getAttribute(attributeName); + } + + @Override + public Collection getHeaderNames(HttpServletResponse httpServletResponse) { + return httpServletResponse.getHeaderNames(); + } + + @Override + public Collection getHeaders(HttpServletResponse httpServletResponse, String headerName) { + return httpServletResponse.getHeaders(headerName); + } + + @Override + public Map getParameterMap(HttpServletRequest httpServletRequest) { + return httpServletRequest.getParameterMap(); + } + + @Override + public boolean isCommitted(HttpServletResponse httpServletResponse) { + return httpServletResponse.isCommitted(); + } + + @Override + public int getStatus(HttpServletResponse httpServletResponse) { + return httpServletResponse.getStatus(); + } + + @Override + public ServletContext getServletContext(HttpServletRequest servletRequest) { + return servletRequest.getServletContext(); + } + + @Nullable + @Override + public InputStream getResourceAsStream(ServletContext servletContext, String path) { + return servletContext.getResourceAsStream(path); + } + + @Override + public TextHeaderGetter getRequestHeaderGetter() { + return JavaxServletRequestHeaderGetter.getInstance(); + } + + @Override + public ServletContext getServletContextFromFilterConfig(FilterConfig filterConfig) { + return filterConfig.getServletContext(); + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletGlobalState.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletAdapter.java similarity index 65% rename from apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletGlobalState.java rename to apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletAdapter.java index 36bae0e033..9e1e39148d 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletGlobalState.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletAdapter.java @@ -16,19 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.servlet; +package co.elastic.apm.agent.servlet.adapter; import co.elastic.apm.agent.sdk.state.GlobalState; -import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; -import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; @GlobalState -public class ServletGlobalState { +public interface ServletAdapter { - public static final WeakMap nameInitialized = WeakConcurrent.buildMap(); + boolean isInstanceOfHttpServlet(Object object); + + ServletContext getServletContextFromServletContextEvent(ServletContextEvent servletContextEvent); + + ServletContext getServletContextFromServletConfig(ServletConfig filterConfig); - // visible for testing as clearing cache is required between tests execution - static void clearServiceNameCache() { - nameInitialized.clear(); - } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletApiAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletApiAdapter.java new file mode 100644 index 0000000000..5f085536a5 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletApiAdapter.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.servlet.adapter; + +import co.elastic.apm.agent.sdk.state.GlobalState; + +@GlobalState +public interface ServletApiAdapter extends + ServletRequestResponseAdapter, + ServletContextAdapter, + ServletAdapter, + FilterAdapter { + +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletContextAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletContextAdapter.java new file mode 100644 index 0000000000..cebda44b24 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletContextAdapter.java @@ -0,0 +1,38 @@ +/* + * 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.servlet.adapter; + +import co.elastic.apm.agent.sdk.state.GlobalState; + +import javax.annotation.Nullable; +import java.io.InputStream; + +@GlobalState +public interface ServletContextAdapter { + @Nullable + ClassLoader getClassLoader(@Nullable ServletContext servletContext); + + String getServletContextName(ServletContext servletContext); + + @Nullable + String getContextPath(ServletContext servletContext); + + @Nullable + InputStream getResourceAsStream(ServletContext servletContext, String path); +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestAdapter.java similarity index 62% rename from apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletHelper.java rename to apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestAdapter.java index 158e0c7c4d..2163d75018 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletHelper.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestAdapter.java @@ -16,44 +16,34 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.servlet; +package co.elastic.apm.agent.servlet.adapter; import co.elastic.apm.agent.impl.context.Request; -import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.sdk.state.GlobalState; import javax.annotation.Nullable; import java.security.Principal; -import java.util.Collection; import java.util.Enumeration; import java.util.Map; -public interface ServletHelper { - - boolean isHttpServletRequest(ServletRequest servletRequest); - - boolean isHttpServletResponse(ServletResponse servletResponse); - - boolean isRequestDispatcherType(ServletRequest servletRequest); - - boolean isAsyncDispatcherType(ServletRequest servletRequest); - - boolean isForwardDispatcherType(ServletRequest servletRequest); - - boolean isIncludeDispatcherType(ServletRequest servletRequest); +@GlobalState +public interface ServletRequestAdapter { + @Nullable + HttpServletRequest asHttpServletRequest(Object servletRequest); - boolean isErrorDispatcherType(ServletRequest servletRequest); + boolean isRequestDispatcherType(HttpServletRequest servletRequest); - @Nullable - ServletContext getServletContext(ServletRequest servletRequest); + boolean isAsyncDispatcherType(HttpServletRequest servletRequest); - ClassLoader getClassloader(ServletContext servletContext); + boolean isForwardDispatcherType(HttpServletRequest servletRequest); - String getServletContextName(ServletContext servletContext); + boolean isIncludeDispatcherType(HttpServletRequest servletRequest); - String getContextPath(ServletContext servletContext); + boolean isErrorDispatcherType(HttpServletRequest servletRequest); @Nullable - Transaction createAndActivateTransaction(HttpServletRequest httpServletRequest); + ServletContext getServletContext(HttpServletRequest servletRequest); void handleCookies(Request request, HttpServletRequest httpServletRequest); @@ -91,24 +81,16 @@ public interface ServletHelper getHeaderNames(HttpServletResponse httpServletResponse); - - Collection getHeaders(HttpServletResponse httpServletResponse, String headerName); - Map getParameterMap(HttpServletRequest httpServletRequest); - boolean isCommitted(HttpServletResponse httpServletResponse); - - int getStatus(HttpServletResponse httpServletResponse); + TextHeaderGetter getRequestHeaderGetter(); } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestResponseAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestResponseAdapter.java new file mode 100644 index 0000000000..0977f1b66d --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestResponseAdapter.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.servlet.adapter; + +import co.elastic.apm.agent.sdk.state.GlobalState; + +@GlobalState +public interface ServletRequestResponseAdapter extends + ServletRequestAdapter, + ServletResponseAdapter { +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletResponseAdapter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletResponseAdapter.java new file mode 100644 index 0000000000..016409fa21 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletResponseAdapter.java @@ -0,0 +1,40 @@ +/* + * 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.servlet.adapter; + +import co.elastic.apm.agent.sdk.state.GlobalState; + +import javax.annotation.Nullable; +import java.util.Collection; + +@GlobalState +public interface ServletResponseAdapter { + + @Nullable + HttpServletResponse asHttpServletResponse(Object servletResponse); + + Collection getHeaderNames(HttpServletResponse httpServletResponse); + + Collection getHeaders(HttpServletResponse httpServletResponse, String headerName); + + boolean isCommitted(HttpServletResponse httpServletResponse); + + int getStatus(HttpServletResponse httpServletResponse); + +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/CommonServletRequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/AbstractServletRequestHeaderGetter.java similarity index 79% rename from apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/CommonServletRequestHeaderGetter.java rename to apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/AbstractServletRequestHeaderGetter.java index 16bacede9f..73beb5cb7e 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/CommonServletRequestHeaderGetter.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/AbstractServletRequestHeaderGetter.java @@ -20,17 +20,9 @@ import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; -import javax.annotation.Nullable; import java.util.Enumeration; -public abstract class CommonServletRequestHeaderGetter implements TextHeaderGetter { - @Nullable - @Override - public String getFirstHeader(String headerName, T carrier) { - return getHeader(headerName, carrier); - } - - abstract String getHeader(String headerName, T carrier); +public abstract class AbstractServletRequestHeaderGetter implements TextHeaderGetter { @Override public void forEach(String headerName, T carrier, S state, HeaderConsumer consumer) { diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletRequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletRequestHeaderGetter.java index 0afa747ff9..69404eeea7 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletRequestHeaderGetter.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletRequestHeaderGetter.java @@ -18,19 +18,21 @@ */ package co.elastic.apm.agent.servlet.helper; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import jakarta.servlet.http.HttpServletRequest; + import java.util.Enumeration; -public class JakartaServletRequestHeaderGetter extends CommonServletRequestHeaderGetter { +public class JakartaServletRequestHeaderGetter extends AbstractServletRequestHeaderGetter { private static final JakartaServletRequestHeaderGetter INSTANCE = new JakartaServletRequestHeaderGetter(); - static CommonServletRequestHeaderGetter getInstance() { + public static TextHeaderGetter getInstance() { return INSTANCE; } @Override - String getHeader(String headerName, HttpServletRequest carrier) { + public String getFirstHeader(String headerName, HttpServletRequest carrier) { return carrier.getHeader(headerName); } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletTransactionCreationHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletTransactionCreationHelper.java deleted file mode 100644 index ec8781424d..0000000000 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletTransactionCreationHelper.java +++ /dev/null @@ -1,70 +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.servlet.helper; - -import co.elastic.apm.agent.impl.ElasticApmTracer; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.HttpServletRequest; - -public class JakartaServletTransactionCreationHelper extends ServletTransactionCreationHelper { - public JakartaServletTransactionCreationHelper(ElasticApmTracer tracer) { - super(tracer); - } - - @Override - protected String getServletPath(HttpServletRequest httpServletRequest) { - return httpServletRequest.getServletPath(); - } - - @Override - protected String getPathInfo(HttpServletRequest httpServletRequest) { - return httpServletRequest.getPathInfo(); - } - - @Override - protected String getHeader(HttpServletRequest httpServletRequest, String headerName) { - return httpServletRequest.getHeader(headerName); - } - - @Override - protected ServletContext getServletContext(HttpServletRequest httpServletRequest) { - return httpServletRequest.getServletContext(); - } - - @Override - protected ClassLoader getClassLoader(ServletContext servletContext) { - return servletContext.getClassLoader(); - } - - @Override - protected CommonServletRequestHeaderGetter getRequestHeaderGetter() { - return JakartaServletRequestHeaderGetter.getInstance(); - } - - @Override - protected String getContextPath(HttpServletRequest httpServletRequest) { - return httpServletRequest.getContextPath(); - } - - @Override - protected String getRequestURI(HttpServletRequest httpServletRequest) { - return httpServletRequest.getRequestURI(); - } -} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletRequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletRequestHeaderGetter.java index cb91aadadb..145e27a8d1 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletRequestHeaderGetter.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletRequestHeaderGetter.java @@ -18,19 +18,21 @@ */ package co.elastic.apm.agent.servlet.helper; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; + import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; -public class JavaxServletRequestHeaderGetter extends CommonServletRequestHeaderGetter { +public class JavaxServletRequestHeaderGetter extends AbstractServletRequestHeaderGetter { private static final JavaxServletRequestHeaderGetter INSTANCE = new JavaxServletRequestHeaderGetter(); - static CommonServletRequestHeaderGetter getInstance() { + public static TextHeaderGetter getInstance() { return INSTANCE; } @Override - String getHeader(String headerName, HttpServletRequest carrier) { + public String getFirstHeader(String headerName, HttpServletRequest carrier) { return carrier.getHeader(headerName); } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletTransactionCreationHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletTransactionCreationHelper.java deleted file mode 100644 index b509778fbe..0000000000 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletTransactionCreationHelper.java +++ /dev/null @@ -1,71 +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.servlet.helper; - -import co.elastic.apm.agent.impl.ElasticApmTracer; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; - -public class JavaxServletTransactionCreationHelper extends ServletTransactionCreationHelper { - - public JavaxServletTransactionCreationHelper(ElasticApmTracer tracer) { - super(tracer); - } - - @Override - protected String getServletPath(HttpServletRequest httpServletRequest) { - return httpServletRequest.getServletPath(); - } - - @Override - protected String getPathInfo(HttpServletRequest httpServletRequest) { - return httpServletRequest.getPathInfo(); - } - - @Override - protected String getHeader(HttpServletRequest httpServletRequest, String headerName) { - return httpServletRequest.getHeader(headerName); - } - - @Override - protected ServletContext getServletContext(HttpServletRequest httpServletRequest) { - return httpServletRequest.getServletContext(); - } - - @Override - protected ClassLoader getClassLoader(ServletContext servletContext) { - return servletContext.getClassLoader(); - } - - @Override - protected CommonServletRequestHeaderGetter getRequestHeaderGetter() { - return JavaxServletRequestHeaderGetter.getInstance(); - } - - @Override - protected String getContextPath(HttpServletRequest httpServletRequest) { - return httpServletRequest.getContextPath(); - } - - @Override - protected String getRequestURI(HttpServletRequest httpServletRequest) { - return httpServletRequest.getRequestURI(); - } -} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java deleted file mode 100644 index 4b472b65be..0000000000 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java +++ /dev/null @@ -1,112 +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.servlet.helper; - -import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.impl.Tracer; -import co.elastic.apm.agent.impl.context.web.WebConfiguration; -import co.elastic.apm.agent.impl.transaction.Transaction; -import co.elastic.apm.agent.matcher.WildcardMatcher; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; - -import javax.annotation.Nullable; - -public abstract class ServletTransactionCreationHelper { - - private static final Logger logger = LoggerFactory.getLogger(ServletTransactionCreationHelper.class); - - private final Tracer tracer; - private final WebConfiguration webConfiguration; - - public ServletTransactionCreationHelper(ElasticApmTracer tracer) { - this.tracer = tracer; - webConfiguration = tracer.getConfig(WebConfiguration.class); - } - - @Nullable - public Transaction createAndActivateTransaction(HTTPREQUEST request) { - // only create a transaction if there is not already one - if (tracer.currentTransaction() != null) { - return null; - } - if (isExcluded(request)) { - return null; - } - ClassLoader cl = getClassloader(getServletContext(request)); - Transaction transaction = tracer.startChildTransaction(request, getRequestHeaderGetter(), cl); - if (transaction != null) { - transaction.activate(); - } - return transaction; - } - - protected abstract String getServletPath(HTTPREQUEST request); - - protected abstract String getPathInfo(HTTPREQUEST request); - - protected abstract String getHeader(HTTPREQUEST request, String headerName); - - protected abstract CONTEXT getServletContext(HTTPREQUEST request); - - protected abstract ClassLoader getClassLoader(CONTEXT servletContext); - - protected abstract CommonServletRequestHeaderGetter getRequestHeaderGetter(); - - protected abstract String getContextPath(HTTPREQUEST request); - - protected abstract String getRequestURI(HTTPREQUEST request); - - boolean isExcluded(HTTPREQUEST request) { - String userAgent = getHeader(request, "User-Agent"); - String requestUri = getRequestURI(request); - - final WildcardMatcher excludeUrlMatcher = WildcardMatcher.anyMatch(webConfiguration.getIgnoreUrls(), requestUri); - - if (excludeUrlMatcher != null && logger.isDebugEnabled()) { - logger.debug("Not tracing this request as the URL {} is ignored by the matcher {}", requestUri, excludeUrlMatcher); - } - final WildcardMatcher excludeAgentMatcher = userAgent != null ? WildcardMatcher.anyMatch(webConfiguration.getIgnoreUserAgents(), userAgent) : null; - if (excludeAgentMatcher != null) { - logger.debug("Not tracing this request as the User-Agent {} is ignored by the matcher {}", userAgent, excludeAgentMatcher); - } - boolean isExcluded = excludeUrlMatcher != null || excludeAgentMatcher != null; - if (!isExcluded && logger.isTraceEnabled()) { - logger.trace("No matcher found for excluding this request with URL: {}, and User-Agent: {}", requestUri, userAgent); - } - return isExcluded; - } - - @Nullable - public ClassLoader getClassloader(@Nullable CONTEXT servletContext) { - if (servletContext == null) { - return null; - } - - // getClassloader might throw UnsupportedOperationException - // see Section 4.4 of the Servlet 3.0 specification - ClassLoader classLoader = null; - try { - return getClassLoader(servletContext); - } catch (UnsupportedOperationException e) { - // silently ignored - return null; - } - } -} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/servicename/InitServiceNameInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/servicename/InitServiceNameInstrumentation.java new file mode 100644 index 0000000000..cbefb1e257 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/servicename/InitServiceNameInstrumentation.java @@ -0,0 +1,135 @@ +/* + * 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.servlet.servicename; + +import co.elastic.apm.agent.servlet.AbstractServletInstrumentation; +import co.elastic.apm.agent.servlet.ServletServiceNameHelper; +import co.elastic.apm.agent.servlet.adapter.JakartaServletApiAdapter; +import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter; +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 javax.annotation.Nullable; + +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.nameEndsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +/** + * Instruments + *
    + *
  • {@link javax.servlet.Filter#init(javax.servlet.FilterConfig)}
  • + *
  • {@link jakarta.servlet.Filter#init(jakarta.servlet.FilterConfig)}
  • + *
  • {@link javax.servlet.Servlet#init(javax.servlet.ServletConfig)}
  • + *
  • {@link jakarta.servlet.Servlet#init(jakarta.servlet.ServletConfig)}
  • + *
  • {@link javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
  • + *
  • {@link jakarta.servlet.ServletContextListener#contextInitialized(jakarta.servlet.ServletContextEvent)}
  • + *
+ * + * Determines the service name based on the webapp's {@code META-INF/MANIFEST.MF} file early in the startup process. + * As this doesn't work with runtime attachment, the service name is also determined when the first request comes in. + */ +public abstract class InitServiceNameInstrumentation extends AbstractServletInstrumentation { + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameContains("Filter").or(nameContains("Servlet")).or(nameContains("Listener")); + } + + @Override + public ElementMatcher getTypeMatcher() { + return not(isInterface()).and(hasSuperType(namedOneOf( + "javax.servlet.ServletContextListener", "javax.servlet.Filter", "javax.servlet.Servlet", + "jakarta.servlet.ServletContextListener", "jakarta.servlet.Filter", "jakarta.servlet.Servlet"))); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("init") + .and(takesArguments(1)) + .and(takesArgument(0, nameEndsWith("Config"))) + .or(named("contextInitialized") + .and(takesArguments(1)) + .and(takesArgument(0, nameEndsWith("ServletContextEvent")))); + } + + public static class JavaxInitServiceNameInstrumentation extends InitServiceNameInstrumentation { + + private static final JavaxServletApiAdapter adapter = JavaxServletApiAdapter.get(); + + @Override + public String rootClassNameThatClassloaderCanLoad() { + return "javax.servlet.AsyncContext"; + } + + public static class AdviceClass { + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void onEnter(@Advice.Argument(0) @Nullable Object arg) { + javax.servlet.ServletContext servletContext; + if (arg instanceof javax.servlet.FilterConfig) { + servletContext = adapter.getServletContextFromFilterConfig((javax.servlet.FilterConfig) arg); + } else if (arg instanceof javax.servlet.ServletConfig) { + servletContext = adapter.getServletContextFromServletConfig((javax.servlet.ServletConfig) arg); + } else if (arg instanceof javax.servlet.ServletContextEvent) { + servletContext = adapter.getServletContextFromServletContextEvent((javax.servlet.ServletContextEvent) arg); + } else { + return; + } + ServletServiceNameHelper.determineServiceName(adapter, servletContext, tracer); + } + } + } + + public static class JakartaInitServiceNameInstrumentation extends InitServiceNameInstrumentation { + + private static final JakartaServletApiAdapter adapter = JakartaServletApiAdapter.get(); + + @Override + public String rootClassNameThatClassloaderCanLoad() { + return "jakarta.servlet.AsyncContext"; + } + + public static class AdviceClass { + + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void onEnter(@Advice.Argument(0) @Nullable Object arg) { + jakarta.servlet.ServletContext servletContext; + if (arg instanceof jakarta.servlet.FilterConfig) { + servletContext = adapter.getServletContextFromFilterConfig((jakarta.servlet.FilterConfig) arg); + } else if (arg instanceof jakarta.servlet.ServletConfig) { + servletContext = adapter.getServletContextFromServletConfig((jakarta.servlet.ServletConfig) arg); + } else if (arg instanceof jakarta.servlet.ServletContextEvent) { + servletContext = adapter.getServletContextFromServletContextEvent((jakarta.servlet.ServletContextEvent) arg); + } else { + return; + } + ServletServiceNameHelper.determineServiceName(adapter, servletContext, tracer); + } + } + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/servicename/ServletContainerInitializerServiceNameInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/servicename/ServletContainerInitializerServiceNameInstrumentation.java new file mode 100644 index 0000000000..0c75c8b323 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/servicename/ServletContainerInitializerServiceNameInstrumentation.java @@ -0,0 +1,114 @@ +/* + * 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.servlet.servicename; + +import co.elastic.apm.agent.servlet.AbstractServletInstrumentation; +import co.elastic.apm.agent.servlet.ServletServiceNameHelper; +import co.elastic.apm.agent.servlet.adapter.JakartaServletApiAdapter; +import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter; +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 javax.annotation.Nullable; +import java.util.Set; + +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.nameEndsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +/** + * Instruments + *
    + *
  • {@link javax.servlet.ServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)}
  • + *
  • {@link jakarta.servlet.ServletContainerInitializer#onStartup(java.util.Set, jakarta.servlet.ServletContext)}
  • + *
+ *

+ * Determines the service name based on the webapp's {@code META-INF/MANIFEST.MF} file early in the startup process. + * As this doesn't work with runtime attachment, the service name is also determined when the first request comes in. + */ +public abstract class ServletContainerInitializerServiceNameInstrumentation extends AbstractServletInstrumentation { + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameContains("Initializer"); + } + + @Override + public ElementMatcher getTypeMatcher() { + return not(isInterface()).and(hasSuperType(namedOneOf( + "javax.servlet.ServletContainerInitializer", "jakarta.servlet.ServletContainerInitializer"))); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("onStartup") + .and(takesArguments(2)) + .and(takesArgument(0, Set.class)) + .and(takesArgument(1, nameEndsWith("ServletContext"))); + } + + public static class JavaxInitServiceNameInstrumentation extends ServletContainerInitializerServiceNameInstrumentation { + + private static final JavaxServletApiAdapter adapter = JavaxServletApiAdapter.get(); + + @Override + public String rootClassNameThatClassloaderCanLoad() { + return "javax.servlet.AsyncContext"; + } + + public static class AdviceClass { + + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void onEnter(@Advice.Argument(1) @Nullable Object servletContext) { + if (servletContext instanceof javax.servlet.ServletContext) { + ServletServiceNameHelper.determineServiceName(adapter, (javax.servlet.ServletContext) servletContext, tracer); + } + } + } + } + + public static class JakartaInitServiceNameInstrumentation extends ServletContainerInitializerServiceNameInstrumentation { + + private static final JakartaServletApiAdapter adapter = JakartaServletApiAdapter.get(); + + @Override + public String rootClassNameThatClassloaderCanLoad() { + return "jakarta.servlet.AsyncContext"; + } + + public static class AdviceClass { + + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void onEnter(@Advice.Argument(1) @Nullable Object servletContext) { + if (servletContext instanceof jakarta.servlet.ServletContext) { + ServletServiceNameHelper.determineServiceName(adapter, (jakarta.servlet.ServletContext) servletContext, tracer); + } + } + } + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-servlet-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 92345979a1..8c80aa5dbb 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -14,3 +14,7 @@ co.elastic.apm.agent.servlet.JakartaAsyncInstrumentation$JakartaStartAsyncInstru co.elastic.apm.agent.servlet.JakartaAsyncInstrumentation$JakartaAsyncContextInstrumentation co.elastic.apm.agent.servlet.JavaxRequestStreamRecordingInstrumentation co.elastic.apm.agent.servlet.JakartaRequestStreamRecordingInstrumentation +co.elastic.apm.agent.servlet.servicename.InitServiceNameInstrumentation$JavaxInitServiceNameInstrumentation +co.elastic.apm.agent.servlet.servicename.InitServiceNameInstrumentation$JakartaInitServiceNameInstrumentation +co.elastic.apm.agent.servlet.servicename.ServletContainerInitializerServiceNameInstrumentation$JavaxInitServiceNameInstrumentation +co.elastic.apm.agent.servlet.servicename.ServletContainerInitializerServiceNameInstrumentation$JakartaInitServiceNameInstrumentation diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java index ba16a36b42..52b405e3a8 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java @@ -46,7 +46,7 @@ abstract class AbstractServletTest extends AbstractInstrumentationTest { void initServerAndClient() throws Exception { // because we reuse the same classloader with different servlet context names // we need to explicitly reset the name cache to make service name detection work as expected - ServletGlobalState.clearServiceNameCache(); + ServletServiceNameHelper.clearServiceNameCache(); // server is not reused between tests as handler is provided from subclass // another alternative diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/CustomManifestLoader.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/CustomManifestLoader.java new file mode 100644 index 0000000000..d8835c5926 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/CustomManifestLoader.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.servlet; + +import org.junit.function.ThrowingRunnable; + +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.function.Supplier; +import java.util.jar.JarFile; + +public class CustomManifestLoader extends URLClassLoader { + private final Supplier manifestSupplier; + + public CustomManifestLoader(Supplier manifestSupplier) { + super(new URL[0]); + this.manifestSupplier = manifestSupplier; + } + + public static void withThreadContextClassLoader(ClassLoader contextClassLoader, ThrowingRunnable runnable) { + ClassLoader previous = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(contextClassLoader); + runnable.run(); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + Thread.currentThread().setContextClassLoader(previous); + } + } + + @Override + public InputStream getResourceAsStream(String name) { + if ((JarFile.MANIFEST_NAME).equals(name)) { + return manifestSupplier.get(); + } + return super.getResourceAsStream(name); + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentationTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentationTest.java new file mode 100644 index 0000000000..e5414a459e --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentationTest.java @@ -0,0 +1,137 @@ +/* + * 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.servlet; + +import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.impl.transaction.TraceContext; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockServletConfig; +import org.springframework.mock.web.MockServletContext; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.Servlet; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import java.io.IOException; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class InitServiceNameInstrumentationTest extends AbstractInstrumentationTest { + + @Test + void testOnStartup() { + ServletContainerInitializer servletContainerInitializer = new NoopServletContainerInitializer(); + + CustomManifestLoader cl = new CustomManifestLoader(() -> getClass().getResourceAsStream("/TEST-MANIFEST.MF")); + CustomManifestLoader.withThreadContextClassLoader(cl, () -> { + servletContainerInitializer.onStartup(null, new MockServletContext()); + tracer.startRootTransaction(cl).end(); + }); + + assertServiceInfo(); + } + + @Test + void testContextInitialized() { + ServletContextListener servletContextListener = new NoopServletContextListener(); + + CustomManifestLoader cl = new CustomManifestLoader(() -> getClass().getResourceAsStream("/TEST-MANIFEST.MF")); + CustomManifestLoader.withThreadContextClassLoader(cl, () -> { + servletContextListener.contextInitialized(new ServletContextEvent(new MockServletContext())); + tracer.startRootTransaction(cl).end(); + }); + + assertServiceInfo(); + } + + @Test + void testServletInit() { + Servlet servlet = new HttpServlet() { + }; + + CustomManifestLoader cl = new CustomManifestLoader(() -> getClass().getResourceAsStream("/TEST-MANIFEST.MF")); + CustomManifestLoader.withThreadContextClassLoader(cl, () -> { + servlet.init(new MockServletConfig()); + tracer.startRootTransaction(cl).end(); + }); + + assertServiceInfo(); + } + + @Test + void testFilterInit() { + Filter filter = new NoopFilter(); + + CustomManifestLoader cl = new CustomManifestLoader(() -> getClass().getResourceAsStream("/TEST-MANIFEST.MF")); + CustomManifestLoader.withThreadContextClassLoader(cl, () -> { + filter.init(new MockFilterConfig()); + tracer.startRootTransaction(cl).end(); + }); + + assertServiceInfo(); + } + + private void assertServiceInfo() { + TraceContext traceContext = reporter.getFirstTransaction().getTraceContext(); + assertThat(traceContext.getServiceName()).isEqualTo("service-name-from-manifest"); + assertThat(traceContext.getServiceVersion()).isEqualTo("1.42.0"); + } + + private static class NoopServletContainerInitializer implements ServletContainerInitializer { + @Override + public void onStartup(Set> classes, ServletContext servletContext) { + } + } + + private static class NoopServletContextListener implements ServletContextListener { + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + } + } + + private static class NoopFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + chain.doFilter(request, response); + } + + @Override + public void destroy() { + + } + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletInstrumentationTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletInstrumentationTest.java index 3b10cdb87e..fb398dcedf 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletInstrumentationTest.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletInstrumentationTest.java @@ -43,7 +43,6 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; -import java.util.Collections; import java.util.EnumSet; import static co.elastic.apm.agent.servlet.RequestDispatcherSpanType.ERROR; diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletServiceNameHelperTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletServiceNameHelperTest.java new file mode 100644 index 0000000000..0adc460651 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletServiceNameHelperTest.java @@ -0,0 +1,87 @@ +/* + * 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.servlet; + +import co.elastic.apm.agent.MockReporter; +import co.elastic.apm.agent.MockTracer; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockServletContext; + +import javax.servlet.ServletContext; + +import static org.assertj.core.api.Assertions.assertThat; + +class ServletServiceNameHelperTest { + + private final MockReporter reporter = new MockReporter(); + private final ElasticApmTracer tracer = MockTracer.createRealTracer(reporter); + + @BeforeEach + void setUp() { + } + + /** + * Tests a scenario of un-deploying a webapp and then re-deploying it on a Servlet container + */ + @Test + void testServiceNameConsistencyAcrossDifferentClassLoaders() { + + ClassLoader cl1 = new CustomManifestLoader(() -> null); + CustomManifestLoader.withThreadContextClassLoader(cl1, () -> { + ServletServiceNameHelper.determineServiceName(JavaxServletApiAdapter.get(), createServletContext(), tracer); + tracer.startRootTransaction(cl1).end(); + }); + + ClassLoader cl2 = new CustomManifestLoader(() -> null); + CustomManifestLoader.withThreadContextClassLoader(cl2, () -> { + ServletServiceNameHelper.determineServiceName(JavaxServletApiAdapter.get(), createServletContext(), tracer); + tracer.startRootTransaction(cl2).end(); + }); + + assertThat(reporter.getTransactions()).hasSize(2); + assertThat(reporter.getTransactions().stream() + .map(t -> t.getTraceContext().getServiceName()) + .distinct()) + .containsExactly("test-context"); + } + + @Test + void testServiceNameFromManifest() { + ClassLoader cl1 = new CustomManifestLoader(() -> getClass().getResourceAsStream("/TEST-MANIFEST.MF")); + CustomManifestLoader.withThreadContextClassLoader(cl1, () -> { + ServletServiceNameHelper.determineServiceName(JavaxServletApiAdapter.get(), createServletContext(), tracer); + tracer.startRootTransaction(cl1).end(); + }); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("service-name-from-manifest"); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("1.42.0"); + } + + @NotNull + private ServletContext createServletContext() { + MockServletContext servletContext = new MockServletContext(); + servletContext.setContextPath("test-context-path"); + servletContext.setServletContextName("test-context"); + return servletContext; + } + +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletTransactionHelperTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletTransactionHelperTest.java index 7de6a9047d..ed2c80eedf 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletTransactionHelperTest.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletTransactionHelperTest.java @@ -28,8 +28,6 @@ import org.junit.jupiter.api.Test; import javax.annotation.Nonnull; -import java.net.URL; -import java.net.URLClassLoader; import java.util.List; import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_LOW_LEVEL_FRAMEWORK; @@ -88,28 +86,6 @@ void testGroupUrlsOverridesServletName() { assertThat(transaction.getNameAsString()).isEqualTo("GET /foo/bar/*"); } - /** - * Tests a scenario of un-deploying a webapp and then re-deploying it on a Servlet container - */ - @Test - void testServiceNameConsistencyAcrossDifferentClassLoaders() { - final String testContext = "test-context"; - final String testContextPath = "test-context-path"; - - URLClassLoader cl1 = new URLClassLoader(new URL[0]); - ServletTransactionHelper.determineServiceName(testContext, cl1, testContextPath); - tracer.startRootTransaction(cl1).end(); - - URLClassLoader cl2 = new URLClassLoader(new URL[0]); - ServletTransactionHelper.determineServiceName(testContext, cl2, testContextPath); - tracer.startRootTransaction(cl2).end(); - - assertThat(reporter.getTransactions().stream() - .filter(transaction -> testContext.equals(transaction.getTraceContext().getServiceName())) - .count() - ).isEqualTo(2); - } - @Nonnull private String getTransactionName(String method, String path) { Transaction transaction = new Transaction(MockTracer.create()); diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletApiAdapterTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletApiAdapterTest.java new file mode 100644 index 0000000000..90cb43d7d4 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletApiAdapterTest.java @@ -0,0 +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.servlet.helper; + +import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter; +import org.junit.jupiter.api.Test; + +import javax.servlet.ServletContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ServletApiAdapterTest { + + public JavaxServletApiAdapter adapter = JavaxServletApiAdapter.get(); + + @Test + void safeGetClassLoader() { + assertThat(adapter.getClassLoader(null)).isNull(); + + ServletContext servletContext = mock(ServletContext.class); + doThrow(UnsupportedOperationException.class).when(servletContext).getClassLoader(); + assertThat(adapter.getClassLoader(servletContext)).isNull(); + + servletContext = mock(ServletContext.class); + ClassLoader cl = mock(ClassLoader.class); + when(servletContext.getClassLoader()).thenReturn(cl); + assertThat(adapter.getClassLoader(servletContext)).isSameAs(cl); + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperTest.java index 9818ff2b06..7d73fb7aae 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperTest.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperTest.java @@ -21,33 +21,29 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.context.web.WebConfiguration; import co.elastic.apm.agent.matcher.WildcardMatcher; +import co.elastic.apm.agent.servlet.ServletTransactionHelper; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.springframework.mock.web.MockHttpServletRequest; import javax.annotation.Nullable; -import javax.servlet.ServletContext; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class ServletTransactionCreationHelperTest extends AbstractInstrumentationTest { private WebConfiguration webConfig; - private ServletTransactionCreationHelper helper; + private ServletTransactionHelper helper; @BeforeEach void setUp() { webConfig = config.getConfig(WebConfiguration.class); - helper = new JavaxServletTransactionCreationHelper(tracer); + helper = new ServletTransactionHelper(tracer); } @ParameterizedTest @@ -74,10 +70,7 @@ void checkRequestPathIgnored(String path, String config, boolean expectIgnored) when(webConfig.getIgnoreUrls()) .thenReturn(parseWildcard(config)); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI(path); - - boolean isIgnored = helper.isExcluded(request); + boolean isIgnored = helper.isExcluded(null, path); assertThat(isIgnored) .describedAs("request with path '%s' %s be ignored", expectIgnored ? "should" : "should not", path) .isEqualTo(expectIgnored); @@ -95,11 +88,7 @@ void requestUserAgentIgnored(String userAgent, String ignoreExpr) { when(webConfig.getIgnoreUserAgents()) .thenReturn(parseWildcard(ignoreExpr)); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI("/request/path"); - request.addHeader("user-agent", userAgent); - - assertThat(helper.isExcluded(request)) + assertThat(helper.isExcluded(userAgent, "/request/path")) .describedAs("request with user-agent '%s' should be ignored", userAgent) .isTrue(); } @@ -113,17 +102,4 @@ private static List parseWildcard(@Nullable String expr) { .collect(Collectors.toList()); } - @Test - void safeGetClassLoader() { - assertThat(helper.getClassloader(null)).isNull(); - - ServletContext servletContext = mock(ServletContext.class); - doThrow(UnsupportedOperationException.class).when(servletContext).getClassLoader(); - assertThat(helper.getClassloader(servletContext)).isNull(); - - servletContext = mock(ServletContext.class); - ClassLoader cl = mock(ClassLoader.class); - when(servletContext.getClassLoader()).thenReturn(cl); - assertThat(helper.getClassloader(servletContext)).isSameAs(cl); - } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/resources/TEST-MANIFEST.MF b/apm-agent-plugins/apm-servlet-plugin/src/test/resources/TEST-MANIFEST.MF new file mode 100644 index 0000000000..1b1d705409 --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/resources/TEST-MANIFEST.MF @@ -0,0 +1,2 @@ +Implementation-Title: service$name-from-manifest +Implementation-Version: 1.42.0 diff --git a/apm-agent-plugins/apm-sparkjava-plugin/pom.xml b/apm-agent-plugins/apm-sparkjava-plugin/pom.xml index 1a45dccae4..c5a38e175a 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 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 fb1a84961b..a649b83cad 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-spring-resttemplate-plugin 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 79be99fff3..bfe841f571 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 10beb28c12..c5c29faa49 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-spring-resttemplate 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 06caf23984..1ec8bf562e 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-spring-webflux-plugin diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java index f52c15be0e..c6f90ee0a1 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java @@ -23,6 +23,7 @@ import co.elastic.apm.agent.impl.context.Request; import co.elastic.apm.agent.impl.context.Url; import co.elastic.apm.agent.impl.context.web.WebConfiguration; +import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; import co.elastic.apm.agent.springwebflux.testapp.GreetingWebClient; @@ -51,6 +52,9 @@ public abstract class AbstractServerInstrumentationTest extends AbstractInstrumentationTest { + // 'elastic:changeme' in base64 + private static final String BASIC_AUTH_HEADER_VALUE = "Basic ZWxhc3RpYzpjaGFuZ2VtZQ=="; + protected static WebFluxApplication.App app; protected GreetingWebClient client; @@ -194,6 +198,8 @@ private Predicate expectClientError(int expectedStatus) { @ParameterizedTest @CsvSource({"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"}) void methodMapping(String method) { + client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE); + var verifier = StepVerifier.create(client.methodMapping(method)); if ("HEAD".equals(method)) { verifier.verifyComplete(); @@ -290,6 +296,81 @@ void childSpansServerSideEvents_shouldNotCreateTransaction() { // reporter.assertNoSpan(200); } + @Test + void testPreauthorized_shouldSuccessWithAuthorizationHeader() { + client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE); + + StepVerifier.create(client.getPreAuthorized(200)) + .expectNext("Hello, elastic!") + .verifyComplete(); + + String expectedName = client.useFunctionalEndpoint() + ? "GET /functional/preauthorized" + : "GreetingAnnotated#getPreauthorized"; + checkTransaction(getFirstTransaction(), expectedName, "GET", 200); + } + + @Test + void testPreauthorized_shouldFailWithoutAuthorization() { + int expectedStatusCode = 500; + boolean checkTransaction = true; + Class expectedExceptionClass = WebClientResponseException.InternalServerError.class; + if (client.useFunctionalEndpoint()) { + expectedStatusCode = 200; + expectedExceptionClass = IllegalStateException.class; + checkTransaction = false; + } + StepVerifier.create(client.getPreAuthorized(expectedStatusCode)) + .expectError(expectedExceptionClass) + .verify(); + + if (checkTransaction) { + String expectedName = client.useFunctionalEndpoint() + ? "GET /functional/preauthorized" + : "GreetingAnnotated#getPreauthorized"; + checkTransaction(getFirstTransaction(), expectedName, "GET", expectedStatusCode); + } else { + // when functional - transaction not created. + } + } + + @Test + void testSecurityContext_shouldSuccessWithAuthorizationHeader() { + client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE); + + StepVerifier.create(client.getSecurityContextUsername(200)) + .expectNext("elastic") + .verifyComplete(); + + String expectedName = client.useFunctionalEndpoint() + ? "GET /functional/username" + : "GreetingAnnotated#getSecurityContextUsername"; + checkTransaction(getFirstTransaction(), expectedName, "GET", 200); + } + + @Test + void testSecurityContextByPath_shouldSuccessWithAuthorizationHeader() { + client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE); + + StepVerifier.create(client.getSecurityContextUsernameByPathSecured(200)) + .expectNext("elastic") + .verifyComplete(); + + String expectedName = client.useFunctionalEndpoint() + ? "GET /functional/path-username" + : "GreetingAnnotated#getSecurityContextUsernameByPathSecured"; + checkTransaction(getFirstTransaction(), expectedName, "GET", 200); + } + + @Test + void testSecurityContextByPath_shouldFailWithoutAuthorizationHeader() { + StepVerifier.create(client.getSecurityContextUsernameByPathSecured(401)) + .expectError(WebClientResponseException.Unauthorized.class) + .verify(); + + // no transactions, not errors captured. + } + private static Predicate> checkSSE(final int index) { return sse -> { String data = sse.data(); @@ -341,6 +422,10 @@ protected Transaction getFirstTransaction() { return reporter.getFirstTransaction(200); } + protected ErrorCapture getFirstError() { + return reporter.getFirstError(200); + } + static Transaction checkTransaction(Transaction transaction, String expectedName, String expectedMethod, int expectedStatus) { assertThat(transaction.getType()).isEqualTo("request"); assertThat(transaction.getNameAsString()).isEqualTo(expectedName); 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 c3746bb2ec..bc43caf827 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-spring-webflux-testapp @@ -60,6 +60,11 @@ spring-boot-starter-tomcat + + org.springframework.boot + spring-boot-starter-security + + com.fasterxml.jackson.core diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java index 6c37dc8aa3..785941cbd9 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java @@ -29,6 +29,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.codec.ServerSentEvent; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; @@ -67,6 +68,24 @@ public Mono getHello(@RequestParam(value = "name", required = false) @Nu return greetingHandler.helloMessage(name); } + @PreAuthorize("hasAuthority('ROLE_USER')") + @RequestMapping("/preauthorized") + public Mono getPreauthorized() { + return greetingHandler.helloMessage("elastic"); + } + + @PreAuthorize("hasAuthority('ROLE_USER')") + @RequestMapping("/username") + public Mono getSecurityContextUsername() { + return greetingHandler.getUsernameFromContext(); + } + + // protected by SecurityWebFilterChain#pathMatchers + @RequestMapping("/path-username") + public Mono getSecurityContextUsernameByPathSecured() { + return greetingHandler.getUsernameFromContext(); + } + @RequestMapping("/error-handler") public Mono handlerError() { // using delayed exception here allows to ensure that the exception handler is properly diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java index 27f771ca73..8c3a70aadb 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java @@ -53,6 +53,12 @@ public RouterFunction route(GreetingHandler greetingHandler) { // 'hello' and 'hello2' are identical, but entry point in builder is not .route(path("/functional/hello"), request -> helloGreeting(greetingHandler, request.queryParam("name"))) + .route(path("/functional/preauthorized"), + request -> helloGreeting(greetingHandler, Optional.of("elastic"))) + .route(path("/functional/username"), + request -> getUsernameFromContext(greetingHandler)) + .route(path("/functional/path-username"), + request -> getUsernameFromContext(greetingHandler)) // .GET("/functional/hello2", accept(MediaType.TEXT_PLAIN), request -> helloGreeting(greetingHandler, request.queryParam("name"))) @@ -120,6 +126,10 @@ private Mono helloGreeting(GreetingHandler greetingHandler, Opti return response(greetingHandler.helloMessage(name.orElse(null))); } + private Mono getUsernameFromContext(GreetingHandler greetingHandler) { + return response(greetingHandler.getUsernameFromContext()); + } + private Mono response(Mono value) { return value.flatMap(s -> ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java index 7d65311e04..e27fdf3874 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java @@ -21,6 +21,7 @@ import co.elastic.apm.agent.impl.GlobalTracer; import co.elastic.apm.agent.impl.transaction.Span; import org.springframework.http.codec.ServerSentEvent; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -94,6 +95,11 @@ public Mono duration(long durationMillis) { .doOnNext(m -> fakeWork(durationMillis)); } + public Mono getUsernameFromContext() { + return ReactiveSecurityContextHolder.getContext() + .flatMap(ctx -> Mono.just(ctx.getAuthentication().getName())); + } + private static void fakeWork(long durationMs) { try { Thread.sleep(durationMs); diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java index ba7d889758..4f74531c75 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java @@ -84,6 +84,12 @@ public Mono getHelloMono() { return requestMono("GET", "/hello", 200); } + public Mono getPreAuthorized(int expectedStatus) { return requestMono("GET", "/preauthorized", expectedStatus); } + + public Mono getSecurityContextUsername(int expectedStatus) { return requestMono("GET", "/username", expectedStatus); } + + public Mono getSecurityContextUsernameByPathSecured(int expectedStatus) { return requestMono("GET", "/path-username", expectedStatus); } + public Mono getMappingError404() { return requestMono("GET", "/error-404", 404); } @@ -155,6 +161,7 @@ public Mono customTransactionName() { public Mono requestMono(String method, String path, int expectedStatus) { Mono request = request(method, path, expectedStatus) .bodyToMono(String.class) + .doOnError((throwable) -> logger.error("Exception occurred during requesting with exception class [{}]", throwable.getClass().getCanonicalName())) .publishOn(clientScheduler); return logEnabled ? request.log(logger) : request; } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java index af75a8d8ad..dcf8a485b3 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java @@ -32,6 +32,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -51,6 +57,8 @@ @Configuration @EnableWebFlux +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity public class WebFluxConfig implements WebFluxConfigurer { // those server methods are just plain copies of ReactiveWebServerFactoryConfiguration inner classes @@ -141,4 +149,28 @@ public WebSocketService webSocketService(@Qualifier("requestUpdateStrategy") Req return new HandshakeWebSocketService(upgradeStrategy); } + @Bean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + return http + .csrf() + .disable() + .authorizeExchange() + .pathMatchers("/annotated/path-username").hasAnyAuthority("ROLE_USER") + .pathMatchers("/functional/path-username", "/functional/username", "/functional/preauthorized").hasAnyAuthority("ROLE_USER") + .pathMatchers("/**").permitAll() + .and() + .httpBasic() + .and() + .build(); + } + + @Bean + public MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withDefaultPasswordEncoder() + .username("elastic") + .password("changeme") + .roles("USER") + .build()); + } } diff --git a/apm-agent-plugins/apm-spring-webflux/pom.xml b/apm-agent-plugins/apm-spring-webflux/pom.xml index ac4b1a3182..aa987e32ad 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-spring-webflux diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/pom.xml b/apm-agent-plugins/apm-spring-webmvc-plugin/pom.xml index 90b5cde7de..3b28a93479 100644 --- a/apm-agent-plugins/apm-spring-webmvc-plugin/pom.xml +++ b/apm-agent-plugins/apm-spring-webmvc-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-spring-webmvc-plugin @@ -16,6 +16,12 @@ + + ${project.groupId} + apm-servlet-plugin + ${project.version} + + javax.servlet javax.servlet-api @@ -28,6 +34,7 @@ ${version.spring} provided + org.springframework spring-test @@ -46,12 +53,6 @@ 2.0.2.RELEASE test - - ${project.groupId} - apm-servlet-plugin - ${project.version} - test - org.hamcrest hamcrest-core diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/ServletWrappingControllerServiceNameInstrumentation.java b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/ServletWrappingControllerTransactionNameInstrumentation.java similarity index 96% rename from apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/ServletWrappingControllerServiceNameInstrumentation.java rename to apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/ServletWrappingControllerTransactionNameInstrumentation.java index d23ae4b383..013a45ced3 100644 --- a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/ServletWrappingControllerServiceNameInstrumentation.java +++ b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/ServletWrappingControllerTransactionNameInstrumentation.java @@ -38,7 +38,7 @@ * to the name of the servlet, * overriding the transaction name set by {@link SpringTransactionNameInstrumentation} that would be {@code ServletWrappingController}. */ -public class ServletWrappingControllerServiceNameInstrumentation extends TracerAwareInstrumentation { +public class ServletWrappingControllerTransactionNameInstrumentation extends TracerAwareInstrumentation { @Override public ElementMatcher getTypeMatcher() { return named("org.springframework.web.servlet.mvc.ServletWrappingController"); diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/SpringServiceNameInstrumentation.java b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/SpringServiceNameInstrumentation.java index 1a5d83de73..60c2ff3a97 100644 --- a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/SpringServiceNameInstrumentation.java +++ b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/SpringServiceNameInstrumentation.java @@ -19,13 +19,14 @@ package co.elastic.apm.agent.springwebmvc; import co.elastic.apm.agent.bci.TracerAwareInstrumentation; +import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.servlet.ServletServiceNameHelper; +import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter; 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 co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletContext; @@ -66,50 +67,47 @@ public String getAdviceClassName() { public static class SpringServiceNameAdvice { - private static final Logger logger = LoggerFactory.getLogger(SpringServiceNameAdvice.class); - @Advice.OnMethodExit(suppress = Throwable.class, inline = false) public static void afterInitPropertySources(@Advice.This WebApplicationContext applicationContext) { + // avoid having two service names for a standalone jar + // one based on Implementation-Title and one based on spring.application.name + if (!ServiceInfo.autoDetected().isMultiServiceContainer()) { + return; + } // This method will be called whenever the spring application context is refreshed which may be more than once // // For example, using Tomcat Servlet container, it's called twice with the first not having a ServletContext, // while the second does, and later requests are initiated with the Servlet classloader and not the application // classloader. ClassLoader classLoader = applicationContext.getClassLoader(); - + ServiceInfo fromServletContext = ServiceInfo.empty(); ServletContext servletContext = applicationContext.getServletContext(); if (servletContext != null) { try { ClassLoader servletClassloader = servletContext.getClassLoader(); if (servletClassloader != null) { classLoader = servletClassloader; + fromServletContext = ServletServiceNameHelper.detectServiceInfo(JavaxServletApiAdapter.get(), servletContext, servletClassloader); } } catch (UnsupportedOperationException e) { // silently ignored } } - String appName = applicationContext.getEnvironment().getProperty("spring.application.name", ""); + ServiceInfo fromSpringApplicationNameProperty = ServiceInfo.of(applicationContext.getEnvironment().getProperty("spring.application.name", "")); + ServiceInfo fromApplicationContextApplicationName = ServiceInfo.of(removeLeadingSlash(applicationContext.getApplicationName())); - if (!appName.isEmpty()) { - if (logger.isDebugEnabled()) { - logger.debug("Setting service name `{}` to be used for class loader [{}], based on the value of " + - "the `spring.application.name` environment variable", appName, classLoader); - } - } else { - // fallback when application name isn't set through an environment property - appName = applicationContext.getApplicationName(); - // remove '/' (if any) from application name - if (appName.startsWith("/")) { - appName = appName.substring(1); - } - if (logger.isDebugEnabled()) { - logger.debug("``spring.application.name` environment variable is not set, falling back to using `{}` " + - "as service name for class loader [{}]", appName, classLoader); - } - } + tracer.overrideServiceInfoForClassLoader(classLoader, fromSpringApplicationNameProperty + .withFallback(fromServletContext) + .withFallback(fromApplicationContextApplicationName)); + } - tracer.overrideServiceNameForClassLoader(classLoader, appName); + private static String removeLeadingSlash(String appName) { + // remove '/' (if any) from application name + if (appName.startsWith("/")) { + appName = appName.substring(1); + } + return appName; } } } diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 8e50e06722..c33046182c 100644 --- a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1,5 +1,5 @@ co.elastic.apm.agent.springwebmvc.SpringTransactionNameInstrumentation -co.elastic.apm.agent.springwebmvc.ServletWrappingControllerServiceNameInstrumentation +co.elastic.apm.agent.springwebmvc.ServletWrappingControllerTransactionNameInstrumentation co.elastic.apm.agent.springwebmvc.ViewRenderInstrumentation co.elastic.apm.agent.springwebmvc.SpringServiceNameInstrumentation co.elastic.apm.agent.springwebmvc.ExceptionHandlerInstrumentation diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java b/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java index 27562e19c8..be66079b97 100644 --- a/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java +++ b/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java @@ -82,7 +82,7 @@ void setup() { @AfterEach final void cleanUp() { - tracer.resetServiceNameOverrides(); + tracer.resetServiceInfoOverrides(); assertThat(tracer.getActive()).isNull(); } diff --git a/apm-agent-plugins/apm-struts-plugin/pom.xml b/apm-agent-plugins/apm-struts-plugin/pom.xml index 1bead19fa1..bbb7aa79ed 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 @@ -37,7 +37,7 @@ javax.servlet javax.servlet-api 3.1.0 - test + provided diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameAdvice.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyAdvice.java similarity index 83% rename from apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameAdvice.java rename to apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyAdvice.java index 59c9fdb1a1..62be176ac1 100644 --- a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameAdvice.java +++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyAdvice.java @@ -21,15 +21,12 @@ import co.elastic.apm.agent.impl.GlobalTracer; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.util.TransactionNameUtils; -import co.elastic.apm.agent.util.VersionUtils; import com.opensymphony.xwork2.ActionProxy; import net.bytebuddy.asm.Advice; import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_HIGH_LEVEL_FRAMEWORK; -public class Struts2TransactionNameAdvice { - - private static final String FRAMEWORK_NAME = "Struts"; +public class ActionProxyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static void setTransactionName(@Advice.This ActionProxy actionProxy) { @@ -38,9 +35,7 @@ public static void setTransactionName(@Advice.This ActionProxy actionProxy) { return; } - transaction.setFrameworkName(FRAMEWORK_NAME); - transaction.setFrameworkVersion(VersionUtils.getVersion(ActionProxy.class, "org.apache.struts", "struts2-core")); - TransactionNameUtils.setNameFromClassAndMethod(actionProxy.getAction().getClass().getSimpleName(), actionProxy.getMethod(), transaction.getAndOverrideName(PRIO_HIGH_LEVEL_FRAMEWORK)); + StrutsFrameworkUtils.setFrameworkNameAndVersion(transaction); } } diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentation.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyInstrumentation.java similarity index 80% rename from apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentation.java rename to apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyInstrumentation.java index 7e941d23b4..de93074ec7 100644 --- a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentation.java +++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyInstrumentation.java @@ -18,20 +18,16 @@ */ package co.elastic.apm.agent.struts; -import co.elastic.apm.agent.bci.TracerAwareInstrumentation; import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import java.util.Collection; - -import static java.util.Collections.singletonList; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.nameContains; import static net.bytebuddy.matcher.ElementMatchers.named; -public class Struts2TransactionNameInstrumentation extends TracerAwareInstrumentation { +public class ActionProxyInstrumentation extends StrutsInstrumentation { @Override public ElementMatcher getTypeMatcherPreFilter() { @@ -48,13 +44,8 @@ public ElementMatcher getMethodMatcher() { return named("execute"); } - @Override - public Collection getInstrumentationGroupNames() { - return singletonList("struts"); - } - @Override public String getAdviceClassName() { - return "co.elastic.apm.agent.struts.Struts2TransactionNameAdvice"; + return "co.elastic.apm.agent.struts.ActionProxyAdvice"; } } diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java new file mode 100644 index 0000000000..304b39237e --- /dev/null +++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java @@ -0,0 +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.struts; + +import co.elastic.apm.agent.impl.GlobalTracer; +import co.elastic.apm.agent.impl.context.web.WebConfiguration; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.util.TransactionNameUtils; +import net.bytebuddy.asm.Advice; + +import javax.servlet.http.HttpServletRequest; + +import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_HIGH_LEVEL_FRAMEWORK; + +public class ExecuteOperationsAdvice { + + private static final WebConfiguration webConfig = GlobalTracer.requireTracerImpl().getConfig(WebConfiguration.class); + + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + public static void setTransactionName(@Advice.Argument(0) HttpServletRequest request, @Advice.Return boolean handled) { + Transaction transaction = GlobalTracer.get().currentTransaction(); + if (!handled || transaction == null) { + return; + } + + StringBuilder transactionName = transaction.getAndOverrideName(PRIO_HIGH_LEVEL_FRAMEWORK); + if (transactionName != null) { + TransactionNameUtils.setNameFromHttpRequestPath(request.getMethod(), request.getServletPath(), transactionName, webConfig.getUrlGroups()); + StrutsFrameworkUtils.setFrameworkNameAndVersion(transaction); + } + } +} diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java new file mode 100644 index 0000000000..e858289c41 --- /dev/null +++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java @@ -0,0 +1,43 @@ +/* + * 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.struts; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class ExecuteOperationsInstrumentation extends StrutsInstrumentation { + + @Override + public ElementMatcher getTypeMatcher() { + return named("org.apache.struts2.dispatcher.ExecuteOperations"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("executeStaticResourceRequest"); + } + + @Override + public String getAdviceClassName() { + return "co.elastic.apm.agent.struts.ExecuteOperationsAdvice"; + } +} diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java new file mode 100644 index 0000000000..12ee4223fb --- /dev/null +++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java @@ -0,0 +1,31 @@ +/* + * 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.struts; + +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.util.VersionUtils; +import com.opensymphony.xwork2.ActionProxy; + +public class StrutsFrameworkUtils { + + public static void setFrameworkNameAndVersion(Transaction transaction) { + transaction.setFrameworkName("Struts"); + transaction.setFrameworkVersion(VersionUtils.getVersion(ActionProxy.class, "org.apache.struts", "struts2-core")); + } +} diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java new file mode 100644 index 0000000000..a86c9b86e7 --- /dev/null +++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java @@ -0,0 +1,33 @@ +/* + * 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.struts; + +import co.elastic.apm.agent.bci.TracerAwareInstrumentation; + +import java.util.Collection; + +import static java.util.Collections.singletonList; + +abstract class StrutsInstrumentation extends TracerAwareInstrumentation { + + @Override + public Collection getInstrumentationGroupNames() { + return singletonList("struts"); + } +} diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 5bc10dbdab..81a0223cef 100644 --- a/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1 +1,2 @@ -co.elastic.apm.agent.struts.Struts2TransactionNameInstrumentation +co.elastic.apm.agent.struts.ActionProxyInstrumentation +co.elastic.apm.agent.struts.ExecuteOperationsInstrumentation diff --git a/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentationTest.java b/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/ActionProxyAdviceTest.java similarity index 96% rename from apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentationTest.java rename to apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/ActionProxyAdviceTest.java index 4089fd2019..c16f3cbd62 100644 --- a/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentationTest.java +++ b/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/ActionProxyAdviceTest.java @@ -28,7 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class Struts2TransactionNameInstrumentationTest extends StrutsTestCase { +public class ActionProxyAdviceTest extends StrutsTestCase { public static class TestAction extends ActionSupport { diff --git a/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml b/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml index da233b0bae..701417cdd9 100644 --- a/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml +++ b/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml @@ -2,12 +2,12 @@ - + 200 - + 200 diff --git a/apm-agent-plugins/apm-urlconnection-plugin/pom.xml b/apm-agent-plugins/apm-urlconnection-plugin/pom.xml index fa1a39de15..f556acddbe 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-urlconnection-plugin 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 c4b506f760..d09e737d55 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 ef7cf2c03f..c4ea5c2f03 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 88ea9a33cd..536f914878 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 decce2840e..b7569f7647 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-vertx4-plugin diff --git a/apm-agent-plugins/apm-vertx/pom.xml b/apm-agent-plugins/apm-vertx/pom.xml index c75170d7e5..4b21068235 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-vertx diff --git a/apm-agent-plugins/pom.xml b/apm-agent-plugins/pom.xml index 2319871c24..522023c69e 100644 --- a/apm-agent-plugins/pom.xml +++ b/apm-agent-plugins/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent-plugins @@ -69,6 +69,8 @@ apm-jaxws-plugin-jakartaee-test apm-jaxrs-plugin-jakartaee-test apm-scheduled-annotation-plugin-jakartaee-test + apm-jakarta-websocket-plugin + apm-ecs-logging-plugin diff --git a/apm-agent/pom.xml b/apm-agent/pom.xml index 6c35eb77f6..169c4e56f6 100644 --- a/apm-agent/pom.xml +++ b/apm-agent/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-parent - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-agent @@ -316,6 +316,16 @@ apm-awslambda-plugin ${project.version} + + ${project.groupId} + apm-jakarta-websocket-plugin + ${project.version} + + + ${project.groupId} + apm-ecs-logging-plugin + ${project.version} + @@ -407,13 +417,6 @@ **/* - - *:* - - **/module-info.class - META-INF/versions/** - - org.apache.logging.log4j:log4j-core diff --git a/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java b/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java index 12aecc7897..ce1ec0d087 100644 --- a/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java +++ b/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java @@ -111,7 +111,7 @@ void testGeneratedConfigurationDocsAreUpToDate() throws IOException, TemplateExc assertThat(renderedDocumentation) .withFailMessage("The rendered configuration documentation (/docs/configuration.asciidoc) is not up-to-date.\n" + "If you see this error on CI, it means you have to execute the tests locally " + - "(./mvnw clean test -pl elastic-apm-agent -am -DfailIfNoTests=false -Dtest=ConfigurationExporterTest) " + + "(./mvnw clean test -pl apm-agent -am -DfailIfNoTests=false -Dtest=ConfigurationExporterTest) " + "which will update the rendered docs.\n" + "If you see this error while running the tests locally, there's nothing more to do - the rendered docs have been updated. " + "When you execute this test the next time, it will not fail anymore.") diff --git a/apm-agent/src/test/resources/configuration.asciidoc.ftl b/apm-agent/src/test/resources/configuration.asciidoc.ftl index 6de0111723..62be8c5bd2 100644 --- a/apm-agent/src/test/resources/configuration.asciidoc.ftl +++ b/apm-agent/src/test/resources/configuration.asciidoc.ftl @@ -54,7 +54,10 @@ Only the external file can be used for dynamic configuration. In order to get started with Elastic APM, the most important configuration options are <>, <> and <>. -So a minimal version of a configuration might look like this: +Note that even these settings are optional. +Click on their name to see how the default values are determined. + +An example configuration looks like this: [source,bash] .System properties @@ -80,12 +83,7 @@ ELASTIC_APM_APPLICATION_PACKAGES=org.example,org.another.example ELASTIC_APM_SERVER_URL=http://localhost:8200 ---- <#assign defaultServiceName> -For Spring-based application, uses the `spring.application.name` property, if set. -For Servlet-based applications, uses the `display-name` of the `web.xml`, if available. -Falls back to the servlet context path the application is mapped to (unless mapped to the root context). -Falls back to the `Implementation-Title` in the `MANIFEST.MF` file. -Falls back to the name of the main class or jar file. -If the service name is set explicitly, it overrides all of the above. +Auto-detected based on the rules described above [float] diff --git a/apm-opentracing/pom.xml b/apm-opentracing/pom.xml index 275382f743..82e3eaf3ac 100644 --- a/apm-opentracing/pom.xml +++ b/apm-opentracing/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT apm-opentracing diff --git a/cloudfoundry/index.yml b/cloudfoundry/index.yml index 85c2ab12c4..29f92ae09f 100644 --- a/cloudfoundry/index.yml +++ b/cloudfoundry/index.yml @@ -36,3 +36,4 @@ 1.28.2: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.28.2/elastic-apm-agent-1.28.2.jar 1.28.3: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.28.3/elastic-apm-agent-1.28.3.jar 1.28.4: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.28.4/elastic-apm-agent-1.28.4.jar +1.29.0: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.29.0/elastic-apm-agent-1.29.0.jar diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index bf5b5a6f95..f77db4ee12 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -57,7 +57,10 @@ Only the external file can be used for dynamic configuration. In order to get started with Elastic APM, the most important configuration options are <>, <> and <>. -So a minimal version of a configuration might look like this: +Note that even these settings are optional. +Click on their name to see how the default values are determined. + +An example configuration looks like this: [source,bash] .System properties @@ -471,21 +474,59 @@ NOTE: Changing this value at runtime can slow down the application temporarily. This is used to keep all the errors and transactions of your service together and is the primary filter in the Elastic APM user interface. +Instead of configuring the service name manually, +you can also choose to rely on the service name auto-detection mechanisms of the agent. +If `service_name` is set explicitly, all auto-detection mechanisms are disabled. + +This is how the service name auto-detection works: + +* For standalone applications +** The agent uses `Implementation-Title` in the `META-INF/MANIFEST.MF` file if the application is started via `java -jar`. +** Falls back to the name of the main class or jar file. +* For applications that are deployed to a servlet container/application server, the agent auto-detects the name for each application. +** For Spring-based applications, the agent uses the `spring.application.name` property, if set. +** For servlet-based applications, falls back to the `Implementation-Title` in the `META-INF/MANIFEST.MF` file. +** Falls back to the `display-name` of the `web.xml`, if available. +** Falls back to the servlet context path the application is mapped to (unless mapped to the root context). + +Generally, it is recommended to rely on the service name detection based on `META-INF/MANIFEST.MF`. +Spring Boot automatically adds the relevant manifest entries. +For other applications that are built with Maven, this is how you add the manifest entries: + +[source,xml] +.pom.xml +---- + + + + + maven-jar-plugin + + + + + true + + + + + + + +---- + The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]+$`. In less regexy terms: Your service name must only contain characters from the ASCII alphabet, numbers, dashes, underscores and spaces. -NOTE: When relying on auto-discovery of the service name in Servlet environments (including Spring Boot), -there is currently a caveat related to metrics. -The consequence is that the 'Metrics' tab of a service does not show process-global metrics like CPU utilization. -The reason is that metrics are reported with the detected default service name for the JVM, -for example `tomcat-application`. -That is because there may be multiple web applications deployed to a single JVM/servlet container. -However, you can view those metrics by selecting the `tomcat-application` service name, for example. -Future versions of the Elastic APM stack will have better support for that scenario. -A workaround is to explicitly set the `service_name` which means all applications deployed to the same servlet container will have the same name -or to disable the corresponding `*-service-name` detecting instrumentations via <>. - NOTE: Service name auto discovery mechanisms require APM Server 7.0+. @@ -494,12 +535,7 @@ NOTE: Service name auto discovery mechanisms require APM Server 7.0+. [options="header"] |============ | Default | Type | Dynamic -| For Spring-based application, uses the `spring.application.name` property, if set. -For Servlet-based applications, uses the `display-name` of the `web.xml`, if available. -Falls back to the servlet context path the application is mapped to (unless mapped to the root context). -Falls back to the `Implementation-Title` in the `MANIFEST.MF` file. -Falls back to the name of the main class or jar file. -If the service name is set explicitly, it overrides all of the above. +| Auto-detected based on the rules described above | String | false |============ @@ -549,6 +585,11 @@ NOTE: Metrics views can utilize this configuration since APM Server 7.5 A version string for the currently deployed version of the service. If you don’t version your deployments, the recommended value for this field is the commit identifier of the deployed revision, e.g. the output of git rev-parse HEAD. +Similar to the auto-detection of <>, the agent can auto-detect the service version based on the `Implementation-Title` attribute in `META-INF/MANIFEST.MF`. +See <> on how to set this attribute. + + + @@ -718,7 +759,7 @@ you should add an additional entry to this list (make sure to also include the d ==== `enable_instrumentations` (added[1.28.0]) A list of instrumentations which should be selectively enabled. -Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`. +Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>. When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>. @@ -746,7 +787,7 @@ NOTE: Changing this value at runtime can slow down the application temporarily. ==== `disable_instrumentations` (added[1.0.0,Changing this value at runtime is possible since version 1.15.0]) A list of instrumentations which should be disabled. -Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`. +Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. For version `1.25.0` and later, use <> to enable experimental instrumentations. NOTE: Changing this value at runtime can slow down the application temporarily. @@ -2870,31 +2911,66 @@ The default unit for this option is `ms`. # This is used to keep all the errors and transactions of your service together # and is the primary filter in the Elastic APM user interface. # +# Instead of configuring the service name manually, +# you can also choose to rely on the service name auto-detection mechanisms of the agent. +# If `service_name` is set explicitly, all auto-detection mechanisms are disabled. +# +# This is how the service name auto-detection works: +# +# * For standalone applications +# ** The agent uses `Implementation-Title` in the `META-INF/MANIFEST.MF` file if the application is started via `java -jar`. +# ** Falls back to the name of the main class or jar file. +# * For applications that are deployed to a servlet container/application server, the agent auto-detects the name for each application. +# ** For Spring-based applications, the agent uses the `spring.application.name` property, if set. +# ** For servlet-based applications, falls back to the `Implementation-Title` in the `META-INF/MANIFEST.MF` file. +# ** Falls back to the `display-name` of the `web.xml`, if available. +# ** Falls back to the servlet context path the application is mapped to (unless mapped to the root context). +# +# Generally, it is recommended to rely on the service name detection based on `META-INF/MANIFEST.MF`. +# Spring Boot automatically adds the relevant manifest entries. +# For other applications that are built with Maven, this is how you add the manifest entries: +# +# +# [source,xml] +# .pom.xml +# ---- +# +# +# +# +# maven-jar-plugin +# +# +# +# +# true +# +# +# +# +# +# +# +# ---- +# +# # The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]+$`. # In less regexy terms: # Your service name must only contain characters from the ASCII alphabet, numbers, dashes, underscores and spaces. # -# NOTE: When relying on auto-discovery of the service name in Servlet environments (including Spring Boot), -# there is currently a caveat related to metrics. -# The consequence is that the 'Metrics' tab of a service does not show process-global metrics like CPU utilization. -# The reason is that metrics are reported with the detected default service name for the JVM, -# for example `tomcat-application`. -# That is because there may be multiple web applications deployed to a single JVM/servlet container. -# However, you can view those metrics by selecting the `tomcat-application` service name, for example. -# Future versions of the Elastic APM stack will have better support for that scenario. -# A workaround is to explicitly set the `service_name` which means all applications deployed to the same servlet container will have the same name -# or to disable the corresponding `*-service-name` detecting instrumentations via <>. -# # NOTE: Service name auto discovery mechanisms require APM Server 7.0+. # # This setting can not be changed at runtime. Changes require a restart of the application. # Type: String -# Default value: For Spring-based application, uses the `spring.application.name` property, if set. -# For Servlet-based applications, uses the `display-name` of the `web.xml`, if available. -# Falls back to the servlet context path the application is mapped to (unless mapped to the root context). -# Falls back to the `Implementation-Title` in the `MANIFEST.MF` file. -# Falls back to the name of the main class or jar file. -# If the service name is set explicitly, it overrides all of the above. +# Default value: Auto-detected based on the rules described above # # # service_name= @@ -2919,6 +2995,11 @@ The default unit for this option is `ms`. # service_node_name= # A version string for the currently deployed version of the service. If you don’t version your deployments, the recommended value for this field is the commit identifier of the deployed revision, e.g. the output of git rev-parse HEAD. +# +# Similar to the auto-detection of <>, the agent can auto-detect the service version based on the `Implementation-Title` attribute in `META-INF/MANIFEST.MF`. +# See <> on how to set this attribute. +# +# # # This setting can not be changed at runtime. Changes require a restart of the application. # Type: String @@ -2999,7 +3080,7 @@ The default unit for this option is `ms`. # sanitize_field_names=password,passwd,pwd,secret,*key,*token*,*session*,*credit*,*card*,*auth*,set-cookie # A list of instrumentations which should be selectively enabled. -# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`. +# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. # When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>. # When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>. # @@ -3012,7 +3093,7 @@ The default unit for this option is `ms`. # enable_instrumentations= # A list of instrumentations which should be disabled. -# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`. +# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. # For version `1.25.0` and later, use <> to enable experimental instrumentations. # # NOTE: Changing this value at runtime can slow down the application temporarily. diff --git a/docs/setup-aws-lambda.asciidoc b/docs/setup-aws-lambda.asciidoc index 9f7cddb1a0..a79523de1d 100644 --- a/docs/setup-aws-lambda.asciidoc +++ b/docs/setup-aws-lambda.asciidoc @@ -9,11 +9,41 @@ and there are various ways you can tweak it to fit your needs. NOTE: In order to get the full AWS Lambda tracing capabilities, use with APM Server 7.16 or higher. Using older versions provides most value, but some relevant metadata fields will not be indexed. +[float] +[[aws-lambda-runtimes]] +==== Supported runtimes + +AWS Lambda provides multiple https://docs.aws.amazon.com/lambda/latest/dg/java-image.html[JVM base images], only the ones that support the `AWS_LAMBDA_EXEC_WRAPPER` environment variables +are supported out of the box. + +Running with unsupported images is still possible but requires providing agent configuration through environment variables +explicitly. + +|=== +|Tags |Java Runtime |Operating System|Supported + +|11 +|Java 11 (Corretto) +|Amazon Linux 2 +|yes + +|8.al2 +|Java 8 (Corretto) +|Amazon Linux 2 +|yes + +|8 +|Java 8 (OpenJDK) +|Amazon Linux 2018.03 +|no + +|=== + [float] [[aws-lambda-installation]] ==== Installation -Setting up your Lambda function is a two-step process. +Setting up APM for your Java Lambda function is a two-step process. Make sure to follow _both_ of the following steps: 1. <> 2. <> @@ -22,37 +52,29 @@ Setting up your Lambda function is a two-step process. [[aws-lambda-extension]] ==== Install the Elastic APM Lambda extension -Elastic uses a Lambda extension to forward data to APM Server in a way that does not interfere with the -execution of your Lambda function. +Elastic uses a Lambda extension to forward data to an APM Server in a way that does not interfere with the execution of your Lambda function. -See the https://www.elastic.co/guide/en/apm/guide/current/aws-lambda-extension.html[installation documentation] to get started. - -NOTE: Not all environment variables specified within https://www.elastic.co/guide/en/apm/guide/current/aws-lambda-extension.html#aws-lambda-variables[The Necessary Variables] -section of the extension documentation need to be configured. The Java agent utilizes a https://docs.aws.amazon.com/lambda/latest/dg/runtimes-modify.html[wrapper script] -to set some of these up, so follow the next section to see which are required. +Follow the https://www.elastic.co/guide/en/apm/guide/current/aws-lambda-extension.html[installation documentation for the extension] to setup the Elastic APM Lambda extension for your Lambda function. [float] [[aws-lambda-instrumenting]] -==== Installing the Elastic APM Java Agent layer - -The Java Agent is installed as an AWS Layer as well. It is distributed as a ZIP archive containing two files: - -- The Java Agent jar file -- A wrapper script that sets up the runtime to enable automatic attachment of the agent - -Follow these steps to ensure proper Java Agent attachment to your Lambda function: - -1. Download the `elastic-apm-java-aws-lambda-layer-.zip` archive from our -https://github.com/elastic/apm-agent-java/releases[GitHub Releases page]. -2. Publish the downloaded ZIP archive as an https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html?icmpid=docs_lambda_help[AWS Lambda Layer]. -3. Configure your function to use the Java Agent layer. -4. Within your Function configuration, configure the following environment variables: -a. `AWS_LAMBDA_EXEC_WRAPPER` (required): set this variable's value to `/opt/elastic-apm-handler`, so that Lambda starts the runtime with this script. -b. `ELASTIC_APM_LAMBDA_APM_SERVER` (required): this will tell the Elastic APM Extension where to send data to. -c. <> or <> (required): one of these needs to be set -as the authentication method that the extension uses when sending data to the URL configured via `ELASTIC_APM_LAMBDA_APM_SERVER` -d. <> (recommended): setting this may improve cold start times. -5. Fine-tune the Java agent with any of the available <>. +==== Install the Elastic APM Java Agent Layer + +The Java Agent is installed as an AWS Layer. + +1. Pick the right ARN from the https://github.com/elastic/apm-agent-java/releases[ARN table for the APM Java Agent Lambda Layer]. Note: The APM Java Agent Layer needs to be in the _same region_ as your Lambda function. +2. Navigate to your function in the AWS Console +3. Scroll to the Layers section and click the _Add a layer_ button +4. Choose the _Specify an ARN_ radio button +5. Enter the Version ARN of the APM Java Agent Layer in the _Specify an ARN_ text input +6. Click the _Add_ button + +Once the APM Java Agent Layer is in place, you'll need to configure some environment variables _in addition_ to the environment variables you already configured for the APM Lambda extension. +Configure the following environment variables for the APM Java Agent Layer: + +1. (required) `AWS_LAMBDA_EXEC_WRAPPER`: set this variable's value to `/opt/elastic-apm-handler`, so that Lambda starts the runtime with an attached APM Java agent. +2. (recommended) <>: setting this may improve cold start times. +3. (optional) Fine-tune the Java agent with any of the available <>. That's it, if you've done all of the above you are set to go! Your Lambda function invocations should be traced from now on. diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index 8e23da8836..b2272ad8eb 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -93,7 +93,7 @@ the JVM arguments. Starting in version 1.18.0, additional spans are created if the servlet dispatches execution to another servlet through the `forward` or `include` APIs, or to an error page. See also <> -|1.0.0 +|1.0.0, 4.0+ (`jakarta.servlet`) since 1.28.0 |Spring Web MVC |4.x, 5.x @@ -111,7 +111,7 @@ the JVM arguments. |2.2.x, 2.3.x, 3.0.x |If you are using JSF, transactions are named based on the requested Facelets and spans are captured for visibility into execution and rendering -|1.0.0 +|1.0.0, `jakarta.faces` since 1.28.0 |Spring Boot |1.5+, 2.x @@ -119,7 +119,7 @@ rendering |1.0.0 |JAX-RS -|2.x +|2.x, 3.x |The transactions are named based on your resources (`ResourceClass#resourceMethod`). Note that only the packages configured in <> are scanned for JAX-RS resources. If you don't set this option, @@ -127,7 +127,7 @@ rendering This comes at the cost of increased startup times, however. Note: JAX-RS is only supported when running on a supported <>. -|1.0.0 +|1.0.0, `jakarta.ws.rs` since 1.28.0 |JAX-WS | @@ -138,7 +138,7 @@ rendering This comes at the cost of increased startup times, however. Note: JAX-WS is only supported when running on a supported <> and when using the HTTP binding. -|1.4.0 +|1.4.0, `jakarta.jws` since 1.28.0 |Grails |3+ @@ -171,6 +171,11 @@ rendering | |1.25.0 +|Java API for WebSocket +|1.0 +|Captures methods annotated with `@OnOpen`, `@OnMessage`, `@OnError`, or `@OnClose` as transactions for classes that are annotated with `@ServerEndpoint`. +|1.29.0 + |=== @@ -398,7 +403,7 @@ same trace. side, the agent reads the context from the Message property through `javax.jms.MessageConsumer#receive`, `javax.jms.MessageConsumer#receiveNoWait`, `javax.jms.JMSConsumer#receive`, `javax.jms.JMSConsumer#receiveNoWait` or `javax.jms.MessageListener#onMessage` and uses it for enabling distributed tracing. -|Enabled by default since 1.13.0, added as an experimental plugin in 1.7.0 +|Enabled by default since 1.13.0, added as an experimental plugin in 1.7.0, `jakarta.jms` since 1.28.0 |Kafka | <0.11.0 - without distributed tracing; 0.11.0+ - full support @@ -449,7 +454,7 @@ When using a scheduling framework a transaction for every execution will be crea `javax.ejb.Schedules` `jakarta.ejb.Schedule` `jakarta.ejb.Schedules` in order to create a transaction with the type `scheduled`, representing the scheduled task execution -|1.6.0 +|1.6.0, `jakarta.ejb.Schedule` since 1.28.0 |Quartz |1.0+ @@ -495,6 +500,8 @@ ECS Reformatting - 2.6+ |When <> is set to `true`, the agent will add a https://logging.apache.org/log4j/2.x/manual/thread-context.html[ThreadContext] entry for `trace.id` and `transaction.id`. +The agent sets the service name for the `EcsLayout` if not provided explicitly (since 1.29.0). + When <> is enabled, logs will be automatically reformatted into ECS-compatible format (since 1.22.0, experimental) @@ -504,6 +511,8 @@ When doing so, the ID corresponding the captured error (`error.id`) is added to Error capturing - 1.10.0 +ECS Service Name - 1.29.0 + ECS Reformatting - 1.22.0 diff --git a/docs/tuning-and-overhead.asciidoc b/docs/tuning-and-overhead.asciidoc index d8aaedbbc4..4eb7382681 100644 --- a/docs/tuning-and-overhead.asciidoc +++ b/docs/tuning-and-overhead.asciidoc @@ -2,6 +2,7 @@ == Overhead and Performance tuning * <> +* <> * <> * <> @@ -68,6 +69,41 @@ The Agent requires some network bandwidth, as it needs to send recorded events t This is where it's important to know how many requests your application handles and how many of those you want to record and store. This can be adjusted with the <>. +[float] +[[tuning-agent-startup]] +=== Tuning the Agent Startup + +When the Java agent starts, it needs to initialize various components of the agent, connect +to the APM server, and instrument any already loaded classes that it has been configured to +trace. This takes some time and resources, and if done synchronously on the main thread (which is +the default when using `-javaagent`) will delay the start of the application until complete. + +We provide several options to tune the startup, targeted at three startup use cases: + +. Immediate synchronous agent start + +The application needs to have instrumentation immediately applied, regardless of startup +time cost - typically because you don't want to miss any traces/transactions right from the +beginning of the application, or some types of actions only happen at initialization and need +to be instrumented before the first instance is created (such as setting up Prepared Statements). +In this use case, use the `-javaagent` command-line flag as per <> +. Fastest start (asynchronously) + +The application can accept instrumentation missing before the application starts +and also accept missing some initial traces and transactions. +In this use case you can attach to the application after startup with <> +or if you are using the `-javaagent` command-line flag you can start the agent asynchronously +by setting the `elastic.apm.start_async` property (since 1.29.0), eg `java -Delastic.apm.start_async ...` +(you can use `elastic.apm.delay_agent_premain_ms=0` in earlier versions) +. Minimized synchronous start + +The application needs to have instrumentation immediately applied, but needs to minimize the +time before the application starts. This requires some tradeoff: in order to reduce the +synchronous startup time, the number of instrumentations applied needs to be minimized +through the `enable_instrumentations` option. +In this use case you should identify the smallest set of instrumentation groups you can +accept for your application monitoring, and use the `enable_instrumentations` configuration +option detailed in the <>. To assist in identifying that +smallest set of instrumentation groups, you can run with logging level set to DEBUG, and +view the statistics produced by the agent on normal termination of the application + [float] [[tuning-agent]] === Tuning the Agent diff --git a/elastic-apm-agent/pom.xml b/elastic-apm-agent/pom.xml index e16ea5f642..3586949dc9 100644 --- a/elastic-apm-agent/pom.xml +++ b/elastic-apm-agent/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-parent - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT elastic-apm-agent @@ -134,6 +134,10 @@ + + + + @@ -208,6 +212,7 @@ true true true + true diff --git a/elastic-apm-agent/src/main/assembly/elastic-apm-handler b/elastic-apm-agent/src/main/assembly/elastic-apm-handler index 177ce7ef60..8081a5fa61 100644 --- a/elastic-apm-agent/src/main/assembly/elastic-apm-handler +++ b/elastic-apm-agent/src/main/assembly/elastic-apm-handler @@ -8,4 +8,5 @@ export ELASTIC_APM_METRICS_INTERVAL="0s" export ELASTIC_APM_CENTRAL_CONFIG="false" export ELASTIC_APM_CLOUD_PROVIDER="none" -exec "$@" \ No newline at end of file +CMD="$(echo "$@" | sed 's/-Xshare:on/-Xshare:auto/g')" +exec $CMD diff --git a/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/AgentMain.java b/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/AgentMain.java index 9cf9b28ae5..4f37a339ce 100644 --- a/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/AgentMain.java +++ b/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/AgentMain.java @@ -90,7 +90,12 @@ public synchronized static void init(String agentArguments, Instrumentation inst if (delayAgentInitMs > 0) { delayAndInitAgentAsync(agentArguments, instrumentation, premain, delayAgentInitMs); } else { - loadAndInitializeAgent(agentArguments, instrumentation, premain); + String startAgentAsyncProperty = System.getProperty("elastic.apm.start_async"); + if (startAgentAsyncProperty != null) { + delayAndInitAgentAsync(agentArguments, instrumentation, premain, 0); + } else { + loadAndInitializeAgent(agentArguments, instrumentation, premain); + } } } @@ -119,7 +124,9 @@ private static void delayAndInitAgentAsync(final String agentArguments, final In public void run() { try { synchronized (AgentMain.class) { - Thread.sleep(delayAgentInitMs); + if (delayAgentInitMs > 0) { + Thread.sleep(delayAgentInitMs); + } loadAndInitializeAgent(agentArguments, instrumentation, premain); } } catch (InterruptedException e) { diff --git a/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/package-info.java b/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/package-info.java index d99e22dbe6..82556c8bb0 100644 --- a/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/package-info.java +++ b/elastic-apm-agent/src/main/java/co/elastic/apm/agent/premain/package-info.java @@ -29,9 +29,10 @@ * *

  • * load the {@code co.elastic.apm.agent.bci.ElasticApmAgent} class and execute the agent initialization process - * through reflection. This can be done synchronously, blocking the bootstrapping thread, or - * asynchronously on a different thread after some delay, that may be configured through the - * {@code elastic.apm.delay_agent_premain_ms} System property. + * through reflection. This can be done synchronously, blocking the bootstrapping thread (default); or + * asynchronously on a different thread with the {@code elastic.apm.start_async} System property (since 1.29.0); + * or asynchronously on a different thread after some delay, by configuring the + * {@code elastic.apm.delay_agent_premain_ms} System property with some positive value. *
  • * */ diff --git a/elastic-apm-agent/src/test/java/co/elastic/apm/agent/premain/AgentPackagingIT.java b/elastic-apm-agent/src/test/java/co/elastic/apm/agent/premain/AgentPackagingIT.java index cc213e2f65..fda9a3b609 100644 --- a/elastic-apm-agent/src/test/java/co/elastic/apm/agent/premain/AgentPackagingIT.java +++ b/elastic-apm-agent/src/test/java/co/elastic/apm/agent/premain/AgentPackagingIT.java @@ -39,6 +39,7 @@ import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import static org.assertj.core.api.Assertions.assertThat; @@ -160,4 +161,11 @@ public String value() { }) .isTrue()); } + + @Test + void testMultiReleaseJar() throws Exception { + JarFile jarFile = new JarFile(agentJar.toFile(), false, ZipFile.OPEN_READ, Runtime.Version.parse("9")); + assertThat(jarFile.isMultiRelease()).isTrue(); + assertThat(jarFile.getJarEntry("agent/org/apache/logging/log4j/util/StackLocator.esclazz").getRealName()).startsWith("META-INF/versions/9"); + } } diff --git a/integration-tests/application-server-integration-tests/pom.xml b/integration-tests/application-server-integration-tests/pom.xml index 2d34881003..25c4bb402c 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT application-server-integration-tests diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/AbstractServletContainerIntegrationTest.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/AbstractServletContainerIntegrationTest.java index d278d2f60a..385f762609 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/AbstractServletContainerIntegrationTest.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/AbstractServletContainerIntegrationTest.java @@ -19,6 +19,8 @@ package co.elastic.apm.servlet; import co.elastic.apm.agent.MockReporter; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.testutils.TestContainersUtils; import co.elastic.apm.servlet.tests.TestApp; import com.fasterxml.jackson.databind.JsonNode; @@ -35,8 +37,6 @@ import org.mockserver.model.ClearType; import org.mockserver.model.HttpRequest; import org.mockserver.model.HttpResponse; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; @@ -598,15 +598,24 @@ private void validateEventMetadata(String bodyAsString) { private void validateServiceName(JsonNode event) { String expectedServiceName = currentTestApp.getExpectedServiceName(); - if (expectedServiceName != null && event != null) { - JsonNode contextService = event.get("context").get("service"); - assertThat(contextService) - .withFailMessage("No service name set. Expected '%s'. Event was %s", expectedServiceName, event) - .isNotNull(); + String expectedServiceVersion = currentTestApp.getExpectedServiceVersion(); + if (event == null || (expectedServiceName == null && expectedServiceVersion == null)) { + return; + } + JsonNode contextService = event.get("context").get("service"); + assertThat(contextService) + .withFailMessage("No service context available.") + .isNotNull(); + if (expectedServiceName != null) { assertThat(contextService.get("name").textValue()) .describedAs("Event has unexpected service name %s", event) .isEqualTo(expectedServiceName); } + if (expectedServiceVersion != null) { + assertThat(contextService.get("version").textValue()) + .describedAs("Event has no service version %s", event) + .isEqualTo(expectedServiceVersion); + } } private void validateMetadataEvent(JsonNode metadata) { diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractJsfApplicationServerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractJsfApplicationServerTestApp.java index 8e67a83fb6..a69ebda294 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractJsfApplicationServerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractJsfApplicationServerTestApp.java @@ -38,7 +38,7 @@ public AbstractJsfApplicationServerTestApp(String servicePath, String deploymentContext, String statusEndpoint, @Nullable String expectedServiceName) { - super(modulePath, appFileName, deploymentContext, statusEndpoint, expectedServiceName); + super(modulePath, appFileName, deploymentContext, statusEndpoint, expectedServiceName, null); this.servicePath = servicePath; } diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractServletApiTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractServletApiTestApp.java index 03f2189052..660d496ec8 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractServletApiTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/AbstractServletApiTestApp.java @@ -41,8 +41,8 @@ public abstract class AbstractServletApiTestApp extends TestApp { private final String servicePath; private final String spanName; - public AbstractServletApiTestApp(String modulePath, String appFileName, String statusEndpoint, String expectedServiceName, String servicePath, String spanName) { - super(modulePath, appFileName, servicePath, statusEndpoint, expectedServiceName); + public AbstractServletApiTestApp(String modulePath, String appFileName, String statusEndpoint, String expectedServiceName, String servicePath, String spanName, String expectedVersion) { + super(modulePath, appFileName, servicePath, statusEndpoint, expectedServiceName, expectedVersion); this.servicePath = servicePath; this.spanName = spanName; } diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiApplicationServerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiApplicationServerTestApp.java index c5d55c6211..23496dde6e 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiApplicationServerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiApplicationServerTestApp.java @@ -33,7 +33,8 @@ public CdiApplicationServerTestApp() { "cdi-app.war", "cdi-app", "status.html", - "CDI App"); + "CDI App", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeApplicationServerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeApplicationServerTestApp.java index 8763f6e0b9..6da59b4206 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeApplicationServerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeApplicationServerTestApp.java @@ -33,7 +33,8 @@ public CdiJakartaeeApplicationServerTestApp() { "cdi-jakartaee-app.war", "cdi-jakartaee-app", "status.html", - "CDI Jakarta App"); + "CDI Jakarta App", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeServletContainerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeServletContainerTestApp.java index 023823753e..ce76984f1c 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeServletContainerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiJakartaeeServletContainerTestApp.java @@ -27,7 +27,8 @@ public CdiJakartaeeServletContainerTestApp() { "cdi-jakartaee-app.war", "cdi-jakartaee-app", "status.html", - "CDI Jakarta App"); + "CDI Jakarta App", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiServletContainerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiServletContainerTestApp.java index 28325350d2..9ef96b8674 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiServletContainerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/CdiServletContainerTestApp.java @@ -27,7 +27,8 @@ public CdiServletContainerTestApp() { "cdi-app.war", "cdi-app", "status.html", - "CDI App"); + "CDI App", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java index e35a40b441..5c07b4cc4f 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.servlet.tests; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.Tracer; import co.elastic.apm.agent.impl.transaction.Outcome; import co.elastic.apm.servlet.AbstractServletContainerIntegrationTest; @@ -45,8 +46,8 @@ protected ExternalPluginTestApp(String testAppModuleName, String appWarFileName) appWarFileName + ".war", appWarFileName, "status.html", - appWarFileName - ); + appWarFileName, + null); this.appWarFileName = appWarFileName; } @@ -91,7 +92,7 @@ private void executeTest(final AbstractServletContainerIntegrationTest container /** * Since we test custom transaction creation through the external plugin, the service name for this transaction cannot be - * captured through the {@link Tracer#overrideServiceNameForClassLoader(java.lang.ClassLoader, java.lang.String)} mechanism. + * captured through the {@link Tracer#overrideServiceInfoForClassLoader(ClassLoader, ServiceInfo)} mechanism. */ @Nullable @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeJsfServletContainerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeJsfServletContainerTestApp.java index ddf0239101..28b3c56cc3 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeJsfServletContainerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeJsfServletContainerTestApp.java @@ -26,7 +26,8 @@ public JakartaeeJsfServletContainerTestApp() { "jakartaee-jsf-http-get.war", "jakartaee-jsf-http-get", "status.html", - "jakartaee-jsf-http-get"); + "jakartaee-jsf-http-get", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeServletApiTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeServletApiTestApp.java index ad493c9715..b44c3c51b1 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeServletApiTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JakartaeeServletApiTestApp.java @@ -21,6 +21,6 @@ public class JakartaeeServletApiTestApp extends AbstractServletApiTestApp { public JakartaeeServletApiTestApp() { - super("../jakartaee-simple-webapp", "jakartaee-simple-webapp.war", "status.jsp", "Simple Web App", "jakartaee-simple-webapp", "JakartaTestApiServlet"); + super("../jakartaee-simple-webapp", "jakartaee-simple-webapp.war", "status.jsp", "Simple Web App", "jakartaee-simple-webapp", "JakartaTestApiServlet", "4.2.0"); } } diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JsfServletContainerTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JsfServletContainerTestApp.java index 50caa839a3..ba82819ce7 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JsfServletContainerTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/JsfServletContainerTestApp.java @@ -26,7 +26,8 @@ public JsfServletContainerTestApp() { "jsf-http-get.war", "jsf-http-get", "status.html", - "jsf-http-get"); + "jsf-http-get", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ServletApiTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ServletApiTestApp.java index d55534f41f..d403c9edd5 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ServletApiTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ServletApiTestApp.java @@ -21,7 +21,7 @@ public class ServletApiTestApp extends AbstractServletApiTestApp { public ServletApiTestApp() { - super("../simple-webapp", "simple-webapp.war", "status.jsp", "Simple Web App", "simple-webapp", "TestApiServlet"); + super("../simple-webapp", "simple-webapp.war", "status.jsp", "Simple Web App", "simple-webapp", "TestApiServlet", "4.2.0"); } } diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/SoapTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/SoapTestApp.java index 3c82935138..d4ea5a5d8a 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/SoapTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/SoapTestApp.java @@ -34,7 +34,8 @@ public SoapTestApp() { "soap-test.war", "soap-test", "status.html", - "soap-test"); + "soap-test", + null); } @Override diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/TestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/TestApp.java index 7b96068b0d..c99046b5c8 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/TestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/TestApp.java @@ -33,10 +33,13 @@ public abstract class TestApp { @Nullable private final String expectedServiceName; private final String deploymentContext; + @Nullable + private final String expectedServiceVersion; - TestApp(String modulePath, String appFileName, String deploymentContext, String statusEndpoint, @Nullable String expectedServiceName) { + TestApp(String modulePath, String appFileName, String deploymentContext, String statusEndpoint, @Nullable String expectedServiceName, @Nullable String expectedVersion) { this.modulePath = modulePath; this.appFileName = appFileName; + this.expectedServiceVersion = expectedVersion; this.statusEndpoint = String.format("/%s/%s", deploymentContext, statusEndpoint); this.deploymentContext = deploymentContext; this.expectedServiceName = expectedServiceName; @@ -92,4 +95,8 @@ public Map getAdditionalEnvVariables() { public abstract void test(AbstractServletContainerIntegrationTest test) throws Exception; + @Nullable + public String getExpectedServiceVersion() { + return expectedServiceVersion; + } } diff --git a/integration-tests/cdi-app/cdi-app-dependent/pom.xml b/integration-tests/cdi-app/cdi-app-dependent/pom.xml index 5fe5c0f97a..09e5034a89 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 97e1284a59..a28ada37ca 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/integration-tests/cdi-app/pom.xml b/integration-tests/cdi-app/pom.xml index 236e9f5724..18eda57159 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 79130e4642..3061a761ef 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 9fb5211acc..cc064d6419 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/integration-tests/cdi-jakartaee-app/pom.xml b/integration-tests/cdi-jakartaee-app/pom.xml index 1e711389d0..da721096a7 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 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 154185c39d..81d617adf0 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 7e98b4fac8..a00b6e3986 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 c2dafdbca7..3d20fb8ee1 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 cfae15aa1c..4ebbac8171 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT plugin-instrumentation-target @@ -20,7 +20,7 @@ org.slf4j slf4j-api - 1.7.25 + 1.7.35 diff --git a/integration-tests/external-plugin-test/pom.xml b/integration-tests/external-plugin-test/pom.xml index 3c70cac77b..2dc0f86b74 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 478bcec9b7..33a1fe6027 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 @@ -24,7 +24,7 @@ jakarta.platform jakarta.jakartaee-api - 9.0.0 + 9.1.0 provided 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 0eb00ad40c..9fc9d70373 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/integration-tests/jakartaee-jsf-app/pom.xml b/integration-tests/jakartaee-jsf-app/pom.xml index 5c9670a1ed..049026ee93 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 pom diff --git a/integration-tests/jakartaee-simple-webapp/pom.xml b/integration-tests/jakartaee-simple-webapp/pom.xml index 4bc9ed47ef..e9c3371bd3 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT jakartaee-simple-webapp @@ -19,6 +19,19 @@ jakartaee-simple-webapp + + + org.apache.maven.plugins + maven-war-plugin + + + + 4.2.0 + + + + + diff --git a/integration-tests/jsf-app/jsf-app-dependent/pom.xml b/integration-tests/jsf-app/jsf-app-dependent/pom.xml index f711583f00..2759940745 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 f454afa13b..81ff88a0e0 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT jsf-app-standalone diff --git a/integration-tests/jsf-app/pom.xml b/integration-tests/jsf-app/pom.xml index 636c50cd08..06d3c680ef 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT jsf-app diff --git a/integration-tests/main-app-test/pom.xml b/integration-tests/main-app-test/pom.xml new file mode 100644 index 0000000000..b9511d8f68 --- /dev/null +++ b/integration-tests/main-app-test/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + integration-tests + co.elastic.apm + 1.29.1-SNAPSHOT + + + main-app-test + ${project.groupId}:${project.artifactId} + + + ${project.basedir}/../.. + + + + + org.slf4j + slf4j-simple + ${version.slf4j} + test + + + org.testcontainers + testcontainers + ${version.testcontainers} + test + + + + + ${project.artifactId} + + + maven-jar-plugin + + + + co.elastic.apm.test.Main + My Service Name + My Service Version + + + + + + + diff --git a/integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java b/integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java new file mode 100644 index 0000000000..649c71543d --- /dev/null +++ b/integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java @@ -0,0 +1,25 @@ +/* + * 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.test; + +public class Main { + + public static void main(String[] args) throws InterruptedException { + } +} diff --git a/integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.java b/integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.java new file mode 100644 index 0000000000..36523532d0 --- /dev/null +++ b/integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.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.test; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +class ServiceIT { + + @ParameterizedTest + @ValueSource(strings = {"openjdk:8", "openjdk:11", "openjdk:17"}) + void testServiceNameAndVersionFromManifest(String image) { + assertThat(new File("target/main-app-test.jar")).exists(); + GenericContainer app = new GenericContainer<>(DockerImageName.parse(image)) + .withFileSystemBind(getAgentJar(), "/tmp/elastic-apm-agent.jar") + .withFileSystemBind("target/main-app-test.jar", "/tmp/main-app.jar") + .withCommand("java -javaagent:/tmp/elastic-apm-agent.jar -jar /tmp/main-app.jar") + .waitingFor(Wait.forLogMessage(".* Starting Elastic APM .*", 1)); + app.start(); + + try { + assertThat(app.getLogs()).contains(" as My Service Name (My Service Version) on "); + } finally { + app.stop(); + } + } + + private static String getAgentJar() { + File buildDir = new File("../../elastic-apm-agent/target/"); + FileFilter fileFilter = file -> file.getName().matches("elastic-apm-agent-\\d\\.\\d+\\.\\d+(\\.RC\\d+)?(-SNAPSHOT)?.jar"); + return Arrays.stream(buildDir.listFiles(fileFilter)) + .findFirst() + .map(File::getAbsolutePath) + .orElseThrow(() -> new IllegalStateException("Agent jar not found. Execute mvn package to build the agent jar.")); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index dd9ff3d4ce..6c0aa99f35 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT integration-tests @@ -25,6 +25,7 @@ external-plugin-test runtime-attach jakartaee-jsf-app + main-app-test diff --git a/integration-tests/runtime-attach/pom.xml b/integration-tests/runtime-attach/pom.xml index 464a2840f7..8459ec2470 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 02013ee8f7..5768c57067 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 10ebb58c80..de0e3c1004 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 4.0.0 diff --git a/integration-tests/simple-webapp/pom.xml b/integration-tests/simple-webapp/pom.xml index 28e147f892..e8e1ed3c6d 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT simple-webapp @@ -20,6 +20,19 @@ simple-webapp + + + org.apache.maven.plugins + maven-war-plugin + + + + 4.2.0 + + + + + diff --git a/integration-tests/soap-test/pom.xml b/integration-tests/soap-test/pom.xml index 288cac2c6a..303aa62290 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT soap-test diff --git a/integration-tests/spring-boot-1-5/pom.xml b/integration-tests/spring-boot-1-5/pom.xml index 9a97468f45..37509b8a7b 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT spring-boot-1-5 diff --git a/integration-tests/spring-boot-2/pom.xml b/integration-tests/spring-boot-2/pom.xml index 21da80d1ca..b415a60d86 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 b652cea441..f179d58bf1 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT spring-boot-2-base diff --git a/integration-tests/spring-boot-2/spring-boot-2-base/src/test/java/co/elastic/apm/spring/boot/AbstractSpringBootTest.java b/integration-tests/spring-boot-2/spring-boot-2-base/src/test/java/co/elastic/apm/spring/boot/AbstractSpringBootTest.java index 9504404734..936a8cd2e5 100644 --- a/integration-tests/spring-boot-2/spring-boot-2-base/src/test/java/co/elastic/apm/spring/boot/AbstractSpringBootTest.java +++ b/integration-tests/spring-boot-2/spring-boot-2-base/src/test/java/co/elastic/apm/spring/boot/AbstractSpringBootTest.java @@ -102,7 +102,9 @@ public void greetingShouldReturnDefaultMessage() throws Exception { assertThat(transaction.getContext().getUser().getId()).isEqualTo("id"); assertThat(transaction.getContext().getUser().getEmail()).isEqualTo("email"); assertThat(transaction.getContext().getUser().getUsername()).isEqualTo("username"); - assertThat(transaction.getTraceContext().getServiceName()).isEqualTo("spring-boot-test"); + // as this test runs in a standalone application and not in a servlet container, + // the service.name will not be overwritten for the webapp class loader based on spring.application.name + assertThat(transaction.getTraceContext().getServiceName()).isNull(); assertThat(transaction.getFrameworkName()).isEqualTo("Spring Web MVC"); assertThat(transaction.getFrameworkVersion()).isEqualTo("5.1.9.RELEASE"); } 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 ca0a79f574..5f3a291099 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 7db9b6f7d4..433ea3f104 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT 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 17bcc0c630..47daf9e22b 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.28.5-SNAPSHOT + 1.29.1-SNAPSHOT spring-boot-2-undertow diff --git a/pom.xml b/pom.xml index 0d4f945b30..272b86f328 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-agent-parent - 1.28.5-SNAPSHOT + 1.29.1-SNAPSHOT pom ${project.groupId}:${project.artifactId} @@ -108,7 +108,7 @@ 5.8.2 1.2.3 4.9.1 - 1.7.32 + 1.7.35 @@ -117,7 +117,7 @@ 5.0.15.RELEASE 2.2.2.RELEASE 9.4.11.v20180605 - 1.0.65 + 1.0.66 1.12.7 9.2 @@ -398,7 +398,10 @@ org.jacoco jacoco-maven-plugin - **/*.esclazz + + **/*.esclazz + **/*.jar + @@ -653,7 +656,7 @@ org.mockito mockito-core - 4.2.0 + 4.3.1 test