diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 27e5901dad..3afb0293dd 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -23,16 +23,23 @@ endif::[]
[[release-notes-1.28.5]]
==== 1.28.5 - YYYY/MM/DD
+[float]
+===== Breaking changes
+* Changes in service name auto-discovery of jar files (see Features section)
+
[float]
===== Features
* Exceptions that are logged using the fatal log level are now captured (log4j2 only) - {pull}2377[#2377]
* 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]
-* Added support for overwritting the service version per classloader - {pull}1726[#1726]
+* 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]
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..35b9b880eb 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,14 +19,17 @@
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;
@@ -37,7 +40,7 @@ public ServiceInfo(@Nullable String serviceName) {
this(serviceName, null);
}
- public ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) {
+ private ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) {
if (serviceName == null || serviceName.trim().isEmpty()) {
this.serviceName = DEFAULT_SERVICE_NAME;
} else {
@@ -46,16 +49,23 @@ 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 of(@Nullable String serviceName, @Nullable String serviceVersion) {
+ if ((serviceName == null || serviceName.isEmpty()) &&
+ (serviceVersion == null || serviceVersion.isEmpty())) {
+ return empty();
+ }
+ return new ServiceInfo(serviceName, serviceVersion);
}
- public static String replaceDisallowedServiceNameChars(String serviceName) {
+ private static String replaceDisallowedServiceNameChars(String serviceName) {
return serviceName.replaceAll("[^a-zA-Z0-9 _-]", "-");
}
@@ -72,7 +82,7 @@ public static ServiceInfo autoDetect(Properties properties) {
if (serviceInfo != null) {
return serviceInfo;
}
- return new ServiceInfo(null);
+ return ServiceInfo.empty();
}
}
@@ -84,12 +94,12 @@ private static ServiceInfo createFromSunJavaCommand(@Nullable String command) {
command = command.trim();
String serviceName = getContainerServiceName(command);
if (serviceName != null) {
- return new ServiceInfo(serviceName);
+ return ServiceInfo.of(serviceName);
}
if (command.contains(".jar")) {
- return parseJarCommand(command);
+ return fromJarCommand(command);
} else {
- return parseMainClass(command);
+ return fromMainClassCommand(command);
}
}
@@ -111,26 +121,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 +161,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 +171,48 @@ 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;
+ }
+
+ 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 serviceName.equals(that.serviceName) && Objects.equals(serviceVersion, that.serviceVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(serviceName, serviceVersion);
+ }
+
+ @Override
+ public String toString() {
+ return "ServiceInfo{" +
+ "name='" + serviceName + '\'' +
+ ", version='" + serviceVersion + '\'' +
+ '}';
+ }
}
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 50faae45c7..e1d6557bb5 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
@@ -41,12 +41,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;
@@ -748,25 +748,19 @@ public List getServiceInfoOverrides() {
}
@Override
- public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
- overrideServiceInfoForClassLoader(classLoader, serviceName, null);
- }
-
- @Override
- public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
+ 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;
}
- ServiceInfo serviceInfo = new ServiceInfo(serviceName, serviceVersion);
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, new ServiceInfo(serviceName, serviceVersion));
+ serviceInfoByClassLoader.putIfAbsent(classLoader, serviceInfo);
}
}
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 344769aca9..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,13 +205,8 @@ public TracerState getState() {
}
@Override
- public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
- tracer.overrideServiceInfoForClassLoader(classLoader, serviceName);
- }
-
- @Override
- public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
- tracer.overrideServiceInfoForClassLoader(classLoader, serviceName, serviceVersion);
+ 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 09dab82d30..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,11 +131,7 @@ public TracerState getState() {
}
@Override
- public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
- }
-
- @Override
- public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
+ 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 43c8b6b9aa..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;
@@ -152,18 +153,6 @@ Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet
TracerState getState();
- /**
- * Overrides the service name 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
- */
- void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName);
-
/**
* 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}.
@@ -172,10 +161,9 @@ Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet
*
*
* @param classLoader the class loader which corresponds to a particular service
- * @param serviceName the service name for this class loader
- * @param serviceVersion the service version for this class loader
+ * @param serviceInfo the service name and version for this class loader
*/
- void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion);
+ void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo);
/**
* Called when the container shuts down.
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 3e650650ce..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
@@ -431,12 +431,15 @@ void testOverrideServiceNameWithoutExplicitServiceName() {
.configurationRegistry(SpyConfiguration.createSpyConfig())
.reporter(reporter)
.buildAndStart();
- tracer.overrideServiceInfoForClassLoader(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.overrideServiceInfoForClassLoader(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.overrideServiceInfoForClassLoader(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,17 +492,24 @@ 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();
- tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden_name", "overridden_version");
+
+ ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version");
+ tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), overridden);
startTestRootTransaction().end();
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ checkServiceInfo(reporter.getFirstTransaction(), overridden);
}
@Test
@@ -500,12 +519,15 @@ void testNotOverrideServiceVersionWhenServiceVersionConfigured() {
.reporter(reporter)
.configurationRegistry(localConfig)
.buildAndStart();
- tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden_name", "overridden_version");
+
+ ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version");
+ ClassLoader cl = getClass().getClassLoader();
+ tracer.overrideServiceInfoForClassLoader(cl, overridden);
+ assertThat(tracer.getServiceInfo(cl)).isEqualTo(overridden);
startTestRootTransaction().end();
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ checkServiceInfo(reporter.getFirstTransaction(), overridden);
}
@Test
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 adc3a10b42..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,6 +19,7 @@
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.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
@@ -40,7 +41,7 @@ void testReportedMetricsUseDefaultServiceNameIfServiceNameIsExplicitlySet() thro
.configurationRegistry(SpyConfiguration.createSpyConfig(SimpleSource.forTest("service_name", "foo")))
.reporter(reporter)
.buildAndStart();
- tracer.overrideServiceInfoForClassLoader(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 e9c7f52083..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
@@ -206,7 +206,7 @@ void testServiceNameAndVersion() throws Exception {
void testServiceNameOverrideWithOneService() throws Exception {
registry.updateTimer("foo", Labels.Mutable.of(), 1);
- JsonNode jsonNode = reportAsJson(singletonList(new ServiceInfo("bar", "1.0")));
+ 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");
@@ -222,7 +222,7 @@ void testServiceNameOverrideWithMultipleService() throws Exception {
registry.flipPhaseAndReport(
metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(
metricSets.values().iterator().next(),
- List.of(new ServiceInfo("bar1", "2.0"), new ServiceInfo("bar2", null))
+ List.of(ServiceInfo.of("bar1", "2.0"), ServiceInfo.of("bar2"))
))
);
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 db9c6af16f..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,32 +329,40 @@ void testManualTimestampsDeactivated() {
@Test
void testOverrideServiceNameForClassLoader() {
- tracer.overrideServiceInfoForClassLoader(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.overrideServiceInfoForClassLoader(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() {
- tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), "overridden_name", "overridden_version");
+ ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version");
+ tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden);
ElasticApm.startTransaction().end();
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ checkTransactionServiceInfo(overridden);
}
@Test
void testOverrideServiceVersionForClassLoaderWithRemoteParent() {
- tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), "overridden_name", "overridden_version");
+ ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version");
+ tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden);
ElasticApm.startTransactionWithRemoteParent(key -> null).end();
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
- assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ 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-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/src/main/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentation.java
new file mode 100644
index 0000000000..4d9ef40f39
--- /dev/null
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentation.java
@@ -0,0 +1,128 @@
+/*
+ * 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.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)}
+ *
+ *
+ * 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 super NamedElement> getTypeMatcherPreFilter() {
+ return nameContains("Filter").or(nameContains("Servlet"));
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return not(isInterface()).and(hasSuperType(namedOneOf("javax.servlet.Filter", "javax.servlet.Servlet", "jakarta.servlet.Filter", "jakarta.servlet.Servlet")));
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return named("init")
+ .and(takesArguments(1))
+ .and(takesArgument(0, nameEndsWith("Config")));
+ }
+
+ 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 config) {
+ if (config == null) {
+ return;
+ }
+ javax.servlet.ServletContext servletContext;
+ if (config instanceof javax.servlet.FilterConfig) {
+ servletContext = adapter.getServletContextFromFilterConfig((javax.servlet.FilterConfig) config);
+ } else if (config instanceof javax.servlet.ServletConfig) {
+ servletContext = adapter.getServletContextFromServletConfig((javax.servlet.ServletConfig) config);
+ } 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 config) {
+ if (config == null) {
+ return;
+ }
+ jakarta.servlet.ServletContext servletContext;
+ if (config instanceof jakarta.servlet.FilterConfig) {
+ servletContext = adapter.getServletContextFromFilterConfig((jakarta.servlet.FilterConfig) config);
+ } else if (config instanceof jakarta.servlet.ServletConfig) {
+ servletContext = adapter.getServletContextFromServletConfig((jakarta.servlet.ServletConfig) config);
+ } else {
+ return;
+ }
+ ServletServiceNameHelper.determineServiceName(adapter, servletContext, tracer);
+ }
+ }
+ }
+}
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..7af1b387be 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..5ddd2011ce
--- /dev/null
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+ }
+ 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));
+ }
+ tracer.overrideServiceInfoForClassLoader(servletContextClassLoader, 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 c7af9e0554..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().overrideServiceInfoForClassLoader(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..976b5409ef
--- /dev/null
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JakartaServletApiAdapter.java
@@ -0,0 +1,283 @@
+/*
+ * 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.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 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..9dbb19faaa
--- /dev/null
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JavaxServletApiAdapter.java
@@ -0,0 +1,283 @@
+/*
+ * 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.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 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..0f5a2ae074 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,15 @@
* 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 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..e8bdbe6d6f
--- /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/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..cb4fab934c 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,5 @@ 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.InitServiceNameInstrumentation$JavaxInitServiceNameInstrumentation
+co.elastic.apm.agent.servlet.InitServiceNameInstrumentation$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..e143bf806e
--- /dev/null
+++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentationTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class InitServiceNameInstrumentationTest extends AbstractInstrumentationTest {
+
+ @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 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-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 super TypeDescription> 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 8ad7cad261..e0922c121d 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.sdk.logging.Logger;
+import co.elastic.apm.agent.sdk.logging.LoggerFactory;
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;
@@ -109,7 +110,7 @@ public static void afterInitPropertySources(@Advice.This WebApplicationContext a
}
}
- tracer.overrideServiceInfoForClassLoader(classLoader, appName);
+ tracer.overrideServiceInfoForClassLoader(classLoader, ServiceInfo.of(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/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 9b6a30c90d..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#overrideServiceInfoForClassLoader(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/jakartaee-simple-webapp/pom.xml b/integration-tests/jakartaee-simple-webapp/pom.xml
index 4bc9ed47ef..8e3717d39c 100644
--- a/integration-tests/jakartaee-simple-webapp/pom.xml
+++ b/integration-tests/jakartaee-simple-webapp/pom.xml
@@ -19,6 +19,19 @@
jakartaee-simple-webapp
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+
+
+ 4.2.0
+
+
+
+
+
diff --git a/integration-tests/simple-webapp/pom.xml b/integration-tests/simple-webapp/pom.xml
index 28e147f892..ae0256215a 100644
--- a/integration-tests/simple-webapp/pom.xml
+++ b/integration-tests/simple-webapp/pom.xml
@@ -20,6 +20,19 @@
simple-webapp
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+
+
+ 4.2.0
+
+
+
+
+