From a43c21b70c93a67275ef7275c1c291404956bde6 Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Thu, 27 Jan 2022 09:04:12 +0100
Subject: [PATCH 01/46] Added support for overwriting the service version per
classloader (#1726)
---
CHANGELOG.asciidoc | 1 +
.../apm/agent/impl/ElasticApmTracer.java | 50 +++++++++++--------
.../elastic/apm/agent/impl/GlobalTracer.java | 9 +++-
.../co/elastic/apm/agent/impl/NoopTracer.java | 6 ++-
.../co/elastic/apm/agent/impl/Tracer.java | 15 +++++-
.../agent/impl/transaction/TraceContext.java | 26 +++++++++-
.../agent/impl/transaction/Transaction.java | 5 +-
.../co/elastic/apm/agent/metrics/Labels.java | 29 ++++++++++-
.../report/serialize/DslJsonSerializer.java | 20 +++++---
.../serialize/MetricRegistryReporter.java | 5 +-
.../serialize/MetricRegistrySerializer.java | 23 +++++----
.../agent/AbstractInstrumentationTest.java | 2 +-
.../apm/agent/impl/ElasticApmTracerTest.java | 36 +++++++++++--
.../apm/agent/impl/SpanTypeBreakdownTest.java | 29 ++++++++---
.../impl/transaction/TraceContextTest.java | 2 +-
.../elastic/apm/agent/metrics/LabelsTest.java | 3 ++
.../serialize/DslJsonSerializerTest.java | 3 ++
.../serialize/MetricRegistryReporterTest.java | 3 +-
.../serialize/MetricSetSerializationTest.java | 37 +++++++++++---
...elasticapm.with-service-version.properties | 1 +
.../api/ElasticApmApiInstrumentationTest.java | 20 +++++++-
.../elastic/apm/agent/profiler/CallTree.java | 10 ++--
.../apm/agent/profiler/SamplingProfiler.java | 23 ++++++++-
.../agent/profiler/CallTreeSpanifyTest.java | 2 +-
.../apm/agent/profiler/CallTreeTest.java | 2 +-
.../servlet/ServletTransactionHelper.java | 2 +-
.../SpringServiceNameInstrumentation.java | 2 +-
...tractViewRenderingInstrumentationTest.java | 2 +-
.../servlet/tests/ExternalPluginTestApp.java | 2 +-
29 files changed, 286 insertions(+), 84 deletions(-)
create mode 100644 apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 894db843b0..c817ca98b5 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -32,6 +32,7 @@ endif::[]
* 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]
[float]
===== Bug fixes
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java
index 4e129f63a3..ccd759dc5a 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
@@ -72,7 +72,7 @@
public class ElasticApmTracer implements Tracer {
private static final Logger logger = LoggerFactory.getLogger(ElasticApmTracer.class);
- private static final WeakMap serviceNameByClassLoader = WeakConcurrent.buildMap();
+ private static final WeakMap serviceInfoByClassLoader = WeakConcurrent.buildMap();
private final ConfigurationRegistry configurationRegistry;
private final StacktraceConfiguration stacktraceConfiguration;
@@ -230,9 +230,10 @@ private void afterTransactionStart(@Nullable ClassLoader initiatingClassLoader,
new RuntimeException("this exception is just used to record where the transaction has been started from"));
}
}
- final String serviceName = getServiceName(initiatingClassLoader);
- if (serviceName != null) {
- transaction.getTraceContext().setServiceName(serviceName);
+ final ServiceInfo serviceInfo = getServiceInfo(initiatingClassLoader);
+ if (serviceInfo != null) {
+ transaction.getTraceContext().setServiceName(serviceInfo.getServiceName());
+ transaction.getTraceContext().setServiceVersion(serviceInfo.getServiceVersion());
}
}
@@ -342,7 +343,11 @@ private ErrorCapture captureException(long epochMicros, @Nullable Throwable e, @
parent.setNonDiscardable();
} else {
error.getTraceContext().getId().setToRandomValue();
- error.getTraceContext().setServiceName(getServiceName(initiatingClassLoader));
+ ServiceInfo serviceInfo = getServiceInfo(initiatingClassLoader);
+ if (serviceInfo != null) {
+ error.getTraceContext().setServiceName(serviceInfo.getServiceName());
+ error.getTraceContext().setServiceVersion(serviceInfo.getServiceVersion());
+ }
}
return error;
}
@@ -734,17 +739,22 @@ public MetricRegistry getMetricRegistry() {
return metricRegistry;
}
- public List getServiceNameOverrides() {
- List serviceNames = new ArrayList<>(serviceNameByClassLoader.approximateSize());
- for (Map.Entry entry : serviceNameByClassLoader) {
- serviceNames.add(entry.getValue());
+ public List getServiceInfoOverrides() {
+ List serviceInfos = new ArrayList<>(serviceInfoByClassLoader.approximateSize());
+ for (Map.Entry entry : serviceInfoByClassLoader) {
+ serviceInfos.add(entry.getValue());
}
- return serviceNames;
+ return serviceInfos;
+ }
+
+ @Override
+ public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
+ overrideServiceInfoForClassLoader(classLoader, serviceName, null);
}
@Override
- public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
- // overriding the service name for the bootstrap class loader is not an actual use-case
+ public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
+ // 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()
@@ -753,23 +763,23 @@ public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader,
return;
}
- String sanitizedServiceName = ServiceInfo.replaceDisallowedServiceNameChars(serviceName);
- logger.debug("Using `{}` as the service name for class loader [{}]", sanitizedServiceName, classLoader);
- if (!serviceNameByClassLoader.containsKey(classLoader)) {
- serviceNameByClassLoader.putIfAbsent(classLoader, sanitizedServiceName);
+ 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));
}
}
@Nullable
- private String getServiceName(@Nullable ClassLoader initiatingClassLoader) {
+ private ServiceInfo getServiceInfo(@Nullable ClassLoader initiatingClassLoader) {
if (initiatingClassLoader == null) {
return null;
}
- return serviceNameByClassLoader.get(initiatingClassLoader);
+ return serviceInfoByClassLoader.get(initiatingClassLoader);
}
- public void resetServiceNameOverrides() {
- serviceNameByClassLoader.clear();
+ public void resetServiceInfoOverrides() {
+ serviceInfoByClassLoader.clear();
}
public ApmServerClient getApmServerClient() {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/GlobalTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/GlobalTracer.java
index a705cc6c74..344769aca9 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
@@ -204,8 +204,13 @@ public TracerState getState() {
}
@Override
- public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
- tracer.overrideServiceNameForClassLoader(classLoader, serviceName);
+ 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);
}
@Override
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/NoopTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/NoopTracer.java
index cfbd816217..09dab82d30 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
@@ -130,7 +130,11 @@ public TracerState getState() {
}
@Override
- public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
+ public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
+ }
+
+ @Override
+ public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
}
@Override
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java
index f17970d64f..43c8b6b9aa 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
@@ -162,7 +162,20 @@ 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
*/
- void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName);
+ 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}.
+ *
+ * The main use case is being able to differentiate between multiple services deployed to the same application server.
+ *
+ *
+ * @param classLoader the class loader which corresponds to a particular service
+ * @param serviceName the service name for this class loader
+ * @param serviceVersion the service version for this class loader
+ */
+ void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion);
/**
* Called when the container shuts down.
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java
index 4c71bf330c..455e6272b6 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java
@@ -241,6 +241,9 @@ public static void copyTraceContextTextHeaders(S source, TextHeaderGetter
@Nullable
private String serviceName;
+ @Nullable
+ private String serviceVersion;
+
private TraceContext(ElasticApmTracer tracer, Id id) {
coreConfiguration = tracer.getConfig(CoreConfiguration.class);
traceState = new TraceState();
@@ -436,6 +439,7 @@ public void asChildOf(TraceContext parent) {
id.setToRandomValue();
clock.init(parent.clock);
serviceName = parent.serviceName;
+ serviceVersion = parent.serviceVersion;
applicationClassLoader = parent.applicationClassLoader;
traceState.copyFrom(parent.traceState);
onMutation();
@@ -452,6 +456,7 @@ public void resetState() {
discardable = true;
clock.resetState();
serviceName = null;
+ serviceVersion = null;
applicationClassLoader = null;
traceState.resetState();
traceState.setSizeLimit(coreConfiguration.getTracestateSizeLimit());
@@ -652,6 +657,7 @@ public void copyFrom(TraceContext other) {
discardable = other.discardable;
clock.init(other.clock);
serviceName = other.serviceName;
+ serviceVersion = other.serviceVersion;
applicationClassLoader = other.applicationClassLoader;
traceState.copyFrom(other.traceState);
onMutation();
@@ -684,6 +690,20 @@ public void setServiceName(@Nullable String serviceName) {
this.serviceName = serviceName;
}
+ @Nullable
+ public String getServiceVersion() {
+ return serviceVersion;
+ }
+
+ /**
+ * Overrides the {@code co.elastic.apm.agent.impl.payload.Service#version} property sent via the meta data Intake V2 event.
+ *
+ * @param serviceVersion the service version for this event
+ */
+ public void setServiceVersion(@Nullable String serviceVersion) {
+ this.serviceVersion = serviceVersion;
+ }
+
public Span createSpan() {
return tracer.startSpan(fromParentContext(), this);
}
@@ -753,7 +773,7 @@ public void serialize(byte[] buffer) {
ByteUtils.putLong(buffer, offset, clock.getOffset());
}
- private void asChildOf(byte[] buffer, @Nullable String serviceName) {
+ private void asChildOf(byte[] buffer, @Nullable String serviceName, @Nullable String serviceVersion) {
int offset = 0;
offset += traceId.fromBytes(buffer, offset);
offset += parentId.fromBytes(buffer, offset);
@@ -763,10 +783,11 @@ private void asChildOf(byte[] buffer, @Nullable String serviceName) {
discardable = buffer[offset++] == (byte) 1;
clock.init(ByteUtils.getLong(buffer, offset));
this.serviceName = serviceName;
+ this.serviceVersion = serviceVersion;
onMutation();
}
- public void deserialize(byte[] buffer, @Nullable String serviceName) {
+ public void deserialize(byte[] buffer, @Nullable String serviceName, @Nullable String serviceVersion) {
int offset = 0;
offset += traceId.fromBytes(buffer, offset);
offset += id.fromBytes(buffer, offset);
@@ -775,6 +796,7 @@ public void deserialize(byte[] buffer, @Nullable String serviceName) {
discardable = buffer[offset++] == (byte) 1;
clock.init(ByteUtils.getLong(buffer, offset));
this.serviceName = serviceName;
+ this.serviceVersion = serviceVersion;
onMutation();
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java
index 71d563a730..00172f9031 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java
@@ -420,7 +420,10 @@ private void trackMetrics() {
}
final Labels.Mutable labels = labelsThreadLocal.get();
labels.resetState();
- labels.serviceName(getTraceContext().getServiceName()).transactionName(name).transactionType(type);
+ labels.serviceName(getTraceContext().getServiceName())
+ .serviceVersion(getTraceContext().getServiceVersion())
+ .transactionName(name)
+ .transactionType(type);
final MetricRegistry metricRegistry = tracer.getMetricRegistry();
long criticalValueAtEnter = metricRegistry.writerCriticalSectionEnter();
try {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java
index bc7ebb1e1c..aeba09da07 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java
@@ -48,6 +48,9 @@ public interface Labels {
@Nullable
String getServiceName();
+ @Nullable
+ String getServiceVersion();
+
@Nullable
CharSequence getTransactionName();
@@ -92,7 +95,7 @@ public List getValues() {
}
public boolean isEmpty() {
- return keys.isEmpty() && getServiceName() == null && getTransactionName() == null && getTransactionType() == null && getSpanType() == null;
+ return keys.isEmpty() && getServiceName() == null && getServiceVersion() == null && getTransactionName() == null && getTransactionType() == null && getSpanType() == null;
}
public int size() {
@@ -117,6 +120,7 @@ public boolean equals(Object o) {
Objects.equals(getTransactionType(), labels.getTransactionType()) &&
contentEquals(getTransactionName(), labels.getTransactionName()) &&
Objects.equals(getServiceName(), labels.getServiceName()) &&
+ Objects.equals(getServiceVersion(), labels.getServiceVersion()) &&
keys.equals(labels.keys) &&
isEqual(values, labels.values);
}
@@ -128,6 +132,7 @@ public int hashCode() {
h = 31 * h + hashEntryAt(i);
}
h = 31 * h + hash(getServiceName());
+ h = 31 * h + hash(getServiceVersion());
h = 31 * h + hash(getTransactionName());
h = 31 * h + (getTransactionType() != null ? getTransactionType().hashCode() : 0);
h = 31 * h + (getSpanType() != null ? getSpanType().hashCode() : 0);
@@ -205,6 +210,8 @@ class Mutable extends AbstractBase implements Recyclable {
@Nullable
private String serviceName;
@Nullable
+ private String serviceVersion;
+ @Nullable
private CharSequence transactionName;
@Nullable
private String transactionType;
@@ -246,6 +253,11 @@ public Labels.Mutable serviceName(@Nullable String serviceName) {
return this;
}
+ public Labels.Mutable serviceVersion(@Nullable String serviceVersion) {
+ this.serviceVersion = serviceVersion;
+ return this;
+ }
+
public Labels.Mutable transactionName(@Nullable CharSequence transactionName) {
this.transactionName = transactionName;
return this;
@@ -271,6 +283,11 @@ public String getServiceName() {
return serviceName;
}
+ @Nullable
+ public String getServiceVersion() {
+ return serviceVersion;
+ }
+
@Nullable
public CharSequence getTransactionName() {
return transactionName;
@@ -301,6 +318,7 @@ public void resetState() {
keys.clear();
values.clear();
serviceName = null;
+ serviceVersion = null;
transactionName = null;
transactionType = null;
spanType = null;
@@ -323,6 +341,8 @@ class Immutable extends AbstractBase {
@Nullable
private final String serviceName;
@Nullable
+ private final String serviceVersion;
+ @Nullable
private final String transactionName;
@Nullable
private final String transactionType;
@@ -334,6 +354,7 @@ class Immutable extends AbstractBase {
public Immutable(Labels labels) {
super(new ArrayList<>(labels.getKeys()), copy(labels.getValues()));
this.serviceName = labels.getServiceName();
+ this.serviceVersion = labels.getServiceVersion();
final CharSequence transactionName = labels.getTransactionName();
this.transactionName = transactionName != null ? transactionName.toString() : null;
this.transactionType = labels.getTransactionType();
@@ -365,6 +386,12 @@ public String getServiceName() {
return serviceName;
}
+ @Nullable
+ @Override
+ public String getServiceVersion() {
+ return serviceVersion;
+ }
+
@Nullable
@Override
public String getTransactionName() {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java
index deea291f50..e6ef5f1c9c 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java
@@ -83,7 +83,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static co.elastic.apm.agent.util.ObjectUtils.defaultIfNull;
import static com.dslplatform.json.JsonWriter.ARRAY_END;
import static com.dslplatform.json.JsonWriter.ARRAY_START;
import static com.dslplatform.json.JsonWriter.COMMA;
@@ -452,10 +451,11 @@ private static void serializeService(final Service service, final StringBuilder
jw.writeByte(JsonWriter.OBJECT_END);
}
- private static void serializeServiceName(final CharSequence serviceName, final StringBuilder replaceBuilder, final JsonWriter jw) {
+ private static void serializeServiceNameAndVersion(final CharSequence serviceName, final CharSequence serviceVersion, final StringBuilder replaceBuilder, final JsonWriter jw) {
if (serviceName != null) {
writeFieldName("service", jw);
jw.writeByte(OBJECT_START);
+ writeField("version", serviceVersion, replaceBuilder, jw);
writeLastField("name", serviceName, replaceBuilder, jw);
jw.writeByte(OBJECT_END);
jw.writeByte(COMMA);
@@ -714,8 +714,9 @@ private void serializeSpan(final Span span) {
private void serializeServiceNameWithFramework(@Nullable final Transaction transaction, final TraceContext traceContext, final ServiceOrigin serviceOrigin) {
String serviceName = traceContext.getServiceName();
+ String serviceVersion = traceContext.getServiceVersion();
boolean isFrameworkNameNotNull = transaction != null && transaction.getFrameworkName() != null;
- if (serviceName != null || isFrameworkNameNotNull || serviceOrigin.hasContent()) {
+ if (serviceName != null || serviceVersion != null || isFrameworkNameNotNull || serviceOrigin.hasContent()) {
writeFieldName("service");
jw.writeByte(OBJECT_START);
if (serviceOrigin.hasContent()) {
@@ -724,7 +725,8 @@ private void serializeServiceNameWithFramework(@Nullable final Transaction trans
if (isFrameworkNameNotNull) {
serializeFramework(transaction.getFrameworkName(), transaction.getFrameworkVersion());
}
- writeLastField("name", serviceName);
+ writeField("name", serviceName);
+ writeLastField("version", serviceVersion);
jw.writeByte(OBJECT_END);
jw.writeByte(COMMA);
}
@@ -924,7 +926,7 @@ private void serializeSpanContext(SpanContext context, TraceContext traceContext
writeFieldName("context");
jw.writeByte(OBJECT_START);
- serializeServiceName(traceContext.getServiceName(), replaceBuilder, jw);
+ serializeServiceNameAndVersion(traceContext.getServiceName(), traceContext.getServiceVersion(), replaceBuilder, jw);
serializeMessageContext(context.getMessage());
serializeDbContext(context.getDb());
serializeHttpContext(context.getHttp());
@@ -1156,8 +1158,12 @@ private static void serializeStringKeyScalarValueMap(Iterator extends Map.Entr
jw.writeByte(OBJECT_END);
}
- static void serializeLabels(Labels labels, final String serviceName, final StringBuilder replaceBuilder, final JsonWriter jw) {
- serializeServiceName(defaultIfNull(labels.getServiceName(), serviceName), replaceBuilder, jw);
+ static void serializeLabels(Labels labels, final String serviceName, final String serviceVersion, final StringBuilder replaceBuilder, final JsonWriter jw) {
+ if (labels.getServiceName() != null) {
+ serializeServiceNameAndVersion(labels.getServiceName(), labels.getServiceVersion(), replaceBuilder, jw);
+ } else {
+ serializeServiceNameAndVersion(serviceName, serviceVersion, replaceBuilder, jw);
+ }
if (!labels.isEmpty()) {
if (labels.getTransactionName() != null || labels.getTransactionType() != null) {
writeFieldName("transaction", jw);
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporter.java
index edeb0c9ce1..144ea8787d 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporter.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporter.java
@@ -18,6 +18,7 @@
*/
package co.elastic.apm.agent.report.serialize;
+import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.context.AbstractLifecycleListener;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.metrics.Labels;
@@ -68,9 +69,9 @@ public void stop() throws Exception {
@Override
public void report(Map extends Labels, MetricSet> metricSets) {
if (tracer.isRunning()) {
- List serviceNames = tracer.getServiceNameOverrides();
+ List serviceInfos = tracer.getServiceInfoOverrides();
for (MetricSet metricSet : metricSets.values()) {
- JsonWriter jw = serializer.serialize(metricSet, serviceNames);
+ JsonWriter jw = serializer.serialize(metricSet, serviceInfos);
if (jw != null) {
reporter.report(jw);
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java
index e33650b51f..cc5ddf7349 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java
@@ -18,6 +18,7 @@
*/
package co.elastic.apm.agent.report.serialize;
+import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.metrics.DoubleSupplier;
import co.elastic.apm.agent.metrics.MetricSet;
import co.elastic.apm.agent.metrics.Timer;
@@ -48,16 +49,18 @@ public class MetricRegistrySerializer {
* @return the serialized metric-set or {@code null} if no samples were serialized
*/
@Nullable
- public JsonWriter serialize(MetricSet metricSet, List serviceNames) {
+ public JsonWriter serialize(MetricSet metricSet, List serviceInfos) {
JsonWriter jw = dslJson.newWriter(maxSerializedSize);
boolean hasSamples = false;
- if (serviceNames.isEmpty() || metricSet.getLabels().getServiceName() != null) {
- hasSamples = serialize(metricSet, null, jw);
+ if (serviceInfos.isEmpty() || metricSet.getLabels().getServiceName() != null) {
+ hasSamples = serialize(metricSet, null, null, jw);
} else {
- hasSamples = serialize(metricSet, serviceNames.get(0), jw);
+ ServiceInfo serviceInfo = serviceInfos.get(0);
+ hasSamples = serialize(metricSet, serviceInfo.getServiceName(), serviceInfo.getServiceVersion(), jw);
if (hasSamples) {
- for (int i = 1; i < serviceNames.size(); ++i) {
- serialize(metricSet, serviceNames.get(i), jw);
+ for (int i = 1; i < serviceInfos.size(); ++i) {
+ serviceInfo = serviceInfos.get(i);
+ serialize(metricSet, serviceInfo.getServiceName(), serviceInfo.getServiceVersion(), jw);
}
}
}
@@ -68,12 +71,12 @@ public JsonWriter serialize(MetricSet metricSet, List serviceNames) {
return null;
}
- private boolean serialize(MetricSet metricSet, String serviceName, JsonWriter jw) {
+ private boolean serialize(MetricSet metricSet, String serviceName, String serviceVersion, JsonWriter jw) {
final long timestamp = System.currentTimeMillis() * 1000;
- return serialize(metricSet, timestamp, serviceName, replaceBuilder, jw);
+ return serialize(metricSet, timestamp, serviceName, serviceVersion, replaceBuilder, jw);
}
- private static boolean serialize(MetricSet metricSet, long epochMicros, String serviceName, StringBuilder replaceBuilder, JsonWriter jw) {
+ private static boolean serialize(MetricSet metricSet, long epochMicros, String serviceName, String serviceVersion, StringBuilder replaceBuilder, JsonWriter jw) {
boolean hasSamples;
jw.writeByte(JsonWriter.OBJECT_START);
{
@@ -83,7 +86,7 @@ private static boolean serialize(MetricSet metricSet, long epochMicros, String s
DslJsonSerializer.writeFieldName("timestamp", jw);
NumberConverter.serialize(epochMicros, jw);
jw.writeByte(JsonWriter.COMMA);
- DslJsonSerializer.serializeLabels(metricSet.getLabels(), serviceName, replaceBuilder, jw);
+ DslJsonSerializer.serializeLabels(metricSet.getLabels(), serviceName, serviceVersion, replaceBuilder, jw);
DslJsonSerializer.writeFieldName("samples", jw);
jw.writeByte(JsonWriter.OBJECT_START);
hasSamples = serializeGauges(metricSet.getGauges(), jw);
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java
index df3379c03b..c01def00c0 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/AbstractInstrumentationTest.java
@@ -118,7 +118,7 @@ public final void cleanUp() {
TracerInternalApiUtils.resumeTracer(tracer);
}
}
- tracer.resetServiceNameOverrides();
+ tracer.resetServiceInfoOverrides();
// reset reporter to default behaviour on all checks
reporter.resetChecks();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java
index 5689160390..3e650650ce 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java
@@ -74,7 +74,7 @@ void setUp() {
void cleanupAndCheck() {
reporter.assertRecycledAfterDecrementingReferences();
objectPoolFactory.checkAllPooledObjectsHaveBeenRecycled();
- tracerImpl.resetServiceNameOverrides();
+ tracerImpl.resetServiceInfoOverrides();
}
@Test
@@ -431,7 +431,7 @@ void testOverrideServiceNameWithoutExplicitServiceName() {
.configurationRegistry(SpyConfiguration.createSpyConfig())
.reporter(reporter)
.buildAndStart();
- tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden");
+ tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden");
startTestRootTransaction().end();
@@ -448,7 +448,7 @@ void testNotOverrideServiceNameWhenServiceNameConfigured() {
.reporter(reporter)
.configurationRegistry(localConfig)
.buildAndStart();
- tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden");
+ tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden");
startTestRootTransaction().end();
@@ -467,7 +467,7 @@ void testNotOverrideServiceNameWhenDefaultServiceNameConfigured() {
.reporter(reporter)
.configurationRegistry(localConfig)
.buildAndStart();
- tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden");
+ tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden");
startTestRootTransaction().end();
CoreConfiguration coreConfig = localConfig.getConfig(CoreConfiguration.class);
@@ -480,6 +480,34 @@ void testNotOverrideServiceNameWhenDefaultServiceNameConfigured() {
}
}
+ @Test
+ void testOverrideServiceVersionWithoutExplicitServiceVersion() {
+ final ElasticApmTracer tracer = new ElasticApmTracerBuilder()
+ .reporter(reporter)
+ .buildAndStart();
+ tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden_name", "overridden_version");
+
+ startTestRootTransaction().end();
+
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ }
+
+ @Test
+ void testNotOverrideServiceVersionWhenServiceVersionConfigured() {
+ ConfigurationRegistry localConfig = SpyConfiguration.createSpyConfig(ConfigSources.fromClasspath("test.elasticapm.with-service-version.properties", ClassLoader.getSystemClassLoader()));
+ final ElasticApmTracer tracer = new ElasticApmTracerBuilder()
+ .reporter(reporter)
+ .configurationRegistry(localConfig)
+ .buildAndStart();
+ tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), "overridden_name", "overridden_version");
+
+ startTestRootTransaction().end();
+
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ }
+
@Test
void testCaptureExceptionAndGetErrorId() {
Transaction transaction = startTestRootTransaction();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java
index 3fdcda01f4..d6eabb05d8 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java
@@ -353,10 +353,26 @@ void testBreakdown_serviceName() {
transaction.end(27);
tracer.getMetricRegistry().flipPhaseAndReport(metricSets -> {
- assertThat(getTimer(metricSets, "span.self_time", "service_name", "app", null).getCount()).isEqualTo(1);
- assertThat(getTimer(metricSets, "span.self_time", "service_name", "app", null).getTotalTimeUs()).isEqualTo(15);
- assertThat(getTimer(metricSets, "span.self_time", "service_name", "db", "mysql").getCount()).isEqualTo(1);
- assertThat(getTimer(metricSets, "span.self_time", "service_name", "db", "mysql").getTotalTimeUs()).isEqualTo(12);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "app", null).getTotalTimeUs()).isEqualTo(15);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "db", "mysql").getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", null, "db", "mysql").getTotalTimeUs()).isEqualTo(12);
+ });
+ }
+
+ @Test
+ void testBreakdown_serviceNameAndVersion() {
+ final Transaction transaction = createTransaction();
+ transaction.getTraceContext().setServiceName("service_name");
+ transaction.getTraceContext().setServiceVersion("service_version");
+ transaction.createSpan(11).withType("db").withSubtype("mysql").end(23);
+ transaction.end(27);
+
+ tracer.getMetricRegistry().flipPhaseAndReport(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "app", null).getTotalTimeUs()).isEqualTo(15);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "db", "mysql").getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "service_name", "service_version", "db", "mysql").getTotalTimeUs()).isEqualTo(12);
});
}
@@ -368,13 +384,14 @@ private Transaction createTransaction() {
@Nullable
private Timer getTimer(Map extends Labels, MetricSet> metricSets, String timerName, @Nullable String spanType, @Nullable String spanSubType) {
- return getTimer(metricSets, timerName, null, spanType, spanSubType);
+ return getTimer(metricSets, timerName, null, null, spanType, spanSubType);
}
@Nullable
- private Timer getTimer(Map extends Labels, MetricSet> metricSets, String timerName, @Nullable String serviceName, @Nullable String spanType, @Nullable String spanSubType) {
+ private Timer getTimer(Map extends Labels, MetricSet> metricSets, String timerName, @Nullable String serviceName, @Nullable String serviceVersion, @Nullable String spanType, @Nullable String spanSubType) {
final MetricSet metricSet = metricSets.get(Labels.Mutable.of()
.serviceName(serviceName)
+ .serviceVersion(serviceVersion)
.transactionName("test")
.transactionType("request")
.spanType(spanType)
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java
index 48e9adea29..b53de50530 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java
@@ -733,7 +733,7 @@ void testDeserialization() {
traceContext.serialize(serializedContext);
TraceContext deserialized = TraceContext.with64BitId(tracer);
- deserialized.deserialize(serializedContext, null);
+ deserialized.deserialize(serializedContext, null, null);
assertThat(deserialized.traceIdAndIdEquals(serializedContext)).isTrue();
assertThat(deserialized.getTraceId()).isEqualTo(traceContext.getTraceId());
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java
index 116219e093..e1f2d90b08 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java
@@ -38,6 +38,9 @@ void testEqualsHashCode() {
assertEqualsHashCode(
Labels.Mutable.of().serviceName("foo"),
Labels.Mutable.of().serviceName("foo"));
+ assertEqualsHashCode(
+ Labels.Mutable.of().serviceVersion("foo"),
+ Labels.Mutable.of().serviceVersion("foo"));
assertEqualsHashCode(
Labels.Mutable.of().transactionName("foo"),
Labels.Mutable.of().transactionName(new StringBuilder("foo")));
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java
index dac0b2d6d9..d140d0471b 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java
@@ -823,10 +823,12 @@ void testTransactionContextSerialization() {
TraceContext ctx = transaction.getTraceContext();
String serviceName = RandomStringUtils.randomAlphabetic(5);
+ String serviceVersion = RandomStringUtils.randomAlphabetic(5);
String frameworkName = RandomStringUtils.randomAlphanumeric(10);
String frameworkVersion = RandomStringUtils.randomNumeric(3);
ctx.setServiceName(serviceName);
+ ctx.setServiceVersion(serviceVersion);
transaction.setFrameworkName(frameworkName);
transaction.setFrameworkVersion(frameworkVersion);
@@ -839,6 +841,7 @@ void testTransactionContextSerialization() {
assertThat(jsonContext.get("user").get("email").asText()).isEqualTo("user@email.com");
assertThat(jsonContext.get("user").get("username").asText()).isEqualTo("bob");
assertThat(jsonContext.get("service").get("name").asText()).isEqualTo(serviceName);
+ assertThat(jsonContext.get("service").get("version").asText()).isEqualTo(serviceVersion);
assertThat(jsonContext.get("service").get("framework").get("name").asText()).isEqualTo(frameworkName);
assertThat(jsonContext.get("service").get("framework").get("version").asText()).isEqualTo(frameworkVersion);
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java
index 44e05c6f36..adc3a10b42 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
@@ -20,7 +20,6 @@
import co.elastic.apm.agent.MockReporter;
import co.elastic.apm.agent.configuration.SpyConfiguration;
-import co.elastic.apm.agent.configuration.source.PropertyFileConfigurationSource;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import com.fasterxml.jackson.databind.JsonNode;
@@ -41,7 +40,7 @@ void testReportedMetricsUseDefaultServiceNameIfServiceNameIsExplicitlySet() thro
.configurationRegistry(SpyConfiguration.createSpyConfig(SimpleSource.forTest("service_name", "foo")))
.reporter(reporter)
.buildAndStart();
- tracer.overrideServiceNameForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), "MetricRegistryReporterTest");
+ tracer.overrideServiceInfoForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), "MetricRegistryReporterTest");
new MetricRegistryReporter(tracer).run();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java
index 7b662ba785..e9c7f52083 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java
@@ -18,6 +18,7 @@
*/
package co.elastic.apm.agent.report.serialize;
+import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.metrics.Labels;
import co.elastic.apm.agent.metrics.MetricRegistry;
import co.elastic.apm.agent.report.ReporterConfiguration;
@@ -188,14 +189,29 @@ void testServiceName() throws Exception {
assertThat(serviceName.asText()).isEqualTo("bar");
}
+ @Test
+ void testServiceNameAndVersion() throws Exception {
+ registry.updateTimer("foo", Labels.Mutable.of().serviceName("bar").serviceVersion("1.0"), 1);
+
+ JsonNode jsonNode = reportAsJson();
+ assertThat(jsonNode).isNotNull();
+ JsonNode service = jsonNode.get("metricset").get("service");
+ JsonNode serviceName = service.get("name");
+ JsonNode serviceVersion = service.get("version");
+ assertThat(serviceName.asText()).isEqualTo("bar");
+ assertThat(serviceVersion.asText()).isEqualTo("1.0");
+ }
+
@Test
void testServiceNameOverrideWithOneService() throws Exception {
registry.updateTimer("foo", Labels.Mutable.of(), 1);
- JsonNode jsonNode = reportAsJson(singletonList("bar"));
+ JsonNode jsonNode = reportAsJson(singletonList(new ServiceInfo("bar", "1.0")));
assertThat(jsonNode).isNotNull();
JsonNode serviceName = jsonNode.get("metricset").get("service").get("name");
assertThat(serviceName.asText()).isEqualTo("bar");
+ JsonNode serviceVersion = jsonNode.get("metricset").get("service").get("version");
+ assertThat(serviceVersion.asText()).isEqualTo("1.0");
}
@Test
@@ -204,22 +220,27 @@ void testServiceNameOverrideWithMultipleService() throws Exception {
final CompletableFuture jwFuture = new CompletableFuture<>();
registry.flipPhaseAndReport(
- metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), List.of("bar1", "bar2")))
+ metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(
+ metricSets.values().iterator().next(),
+ List.of(new ServiceInfo("bar1", "2.0"), new ServiceInfo("bar2", null))
+ ))
);
String[] jsonStrings = jwFuture.getNow(null).toString().split("\n");
assertThat(jsonStrings.length).isEqualTo(2);
JsonNode jsonNode1 = objectMapper.readTree(jsonStrings[0]);
- String serviceName1 = jsonNode1.get("metricset").get("service").get("name").asText();
- assertThat(serviceName1).isEqualTo("bar1");
+ JsonNode service1 = jsonNode1.get("metricset").get("service");
+ assertThat(service1.get("name").asText()).isEqualTo("bar1");
+ assertThat(service1.get("version").asText()).isEqualTo("2.0");
JsonNode samples1 = jsonNode1.get("metricset").get("samples");
assertThat(samples1.get("foo.sum.us").get("value").intValue()).isOne();
assertThat(samples1.get("foo.count").get("value").intValue()).isOne();
JsonNode jsonNode2 = objectMapper.readTree(jsonStrings[1]);
- String serviceName2 = jsonNode2.get("metricset").get("service").get("name").asText();
- assertThat(serviceName2).isEqualTo("bar2");
+ JsonNode service2 = jsonNode2.get("metricset").get("service");
+ assertThat(service2.get("name").asText()).isEqualTo("bar2");
+ assertThat(service2.get("version")).isNull();
JsonNode samples2 = jsonNode2.get("metricset").get("samples");
assertThat(samples2.get("foo.sum.us").get("value").intValue()).isOne();
assertThat(samples2.get("foo.count").get("value").intValue()).isOne();
@@ -231,10 +252,10 @@ private JsonNode reportAsJson() throws Exception {
}
@Nullable
- private JsonNode reportAsJson(List serviceNames) throws Exception {
+ private JsonNode reportAsJson(List serviceInfos) throws Exception {
final CompletableFuture jwFuture = new CompletableFuture<>();
registry.flipPhaseAndReport(
- metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), serviceNames))
+ metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), serviceInfos))
);
JsonNode json = null;
JsonWriter jw = jwFuture.getNow(null);
diff --git a/apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties b/apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties
new file mode 100644
index 0000000000..908f6c963c
--- /dev/null
+++ b/apm-agent-core/src/test/resources/test.elasticapm.with-service-version.properties
@@ -0,0 +1 @@
+service_version=TEST_SERVICE_VERSION
diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java
index 214b0642de..db9c6af16f 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
@@ -327,18 +327,34 @@ void testManualTimestampsDeactivated() {
@Test
void testOverrideServiceNameForClassLoader() {
- tracer.overrideServiceNameForClassLoader(Transaction.class.getClassLoader(), "overridden");
+ tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), "overridden");
ElasticApm.startTransaction().end();
assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden");
}
@Test
void testOverrideServiceNameForClassLoaderWithRemoteParent() {
- tracer.overrideServiceNameForClassLoader(Transaction.class.getClassLoader(), "overridden");
+ tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), "overridden");
ElasticApm.startTransactionWithRemoteParent(key -> null).end();
assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden");
}
+ @Test
+ void testOverrideServiceVersionForClassLoader() {
+ tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), "overridden_name", "overridden_version");
+ ElasticApm.startTransaction().end();
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ }
+
+ @Test
+ void testOverrideServiceVersionForClassLoaderWithRemoteParent() {
+ tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), "overridden_name", "overridden_version");
+ ElasticApm.startTransactionWithRemoteParent(key -> null).end();
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden_name");
+ assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("overridden_version");
+ }
+
@Test
void testFrameworkNameWithStartTransactionWithRemoteParent() {
ElasticApm.startTransactionWithRemoteParent(null).end();
diff --git a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java
index 6decb709a9..c5966f7e3c 100644
--- a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java
+++ b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java
@@ -148,9 +148,9 @@ private boolean happenedAfter(long timestamp) {
return lastSeen < timestamp;
}
- public static CallTree.Root createRoot(ObjectPool rootPool, byte[] traceContext, @Nullable String serviceName, long nanoTime) {
+ public static CallTree.Root createRoot(ObjectPool rootPool, byte[] traceContext, @Nullable String serviceName, @Nullable String serviceVersion, long nanoTime) {
CallTree.Root root = rootPool.createInstance();
- root.set(traceContext, serviceName, nanoTime);
+ root.set(traceContext, serviceName, serviceVersion, nanoTime);
return root;
}
@@ -596,9 +596,9 @@ public Root(ElasticApmTracer tracer) {
this.rootContext = TraceContext.with64BitId(tracer);
}
- private void set(byte[] traceContext, @Nullable String serviceName, long nanoTime) {
+ private void set(byte[] traceContext, @Nullable String serviceName, @Nullable String serviceVersion, long nanoTime) {
super.set(null, ROOT_FRAME, nanoTime);
- this.rootContext.deserialize(traceContext, serviceName);
+ this.rootContext.deserialize(traceContext, serviceName, serviceVersion);
setActiveSpan(traceContext, nanoTime);
}
@@ -660,7 +660,7 @@ public void addStackTrace(ElasticApmTracer tracer, List stackTrace,
if (activeSpan == null) {
firstFrameAfterActivation = true;
activeSpan = TraceContext.with64BitId(tracer);
- activeSpan.deserialize(activeSpanSerialized, rootContext.getServiceName());
+ activeSpan.deserialize(activeSpanSerialized, rootContext.getServiceName(), rootContext.getServiceVersion());
}
previousTopOfStack = topOfStack;
topOfStack = addFrame(stackTrace, stackTrace.size(), activeSpan, activationTimestamp, nanoTime, callTreePool, minDurationNs, this);
diff --git a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java
index d288220b13..d4543002fb 100644
--- a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java
+++ b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/SamplingProfiler.java
@@ -777,6 +777,7 @@ private static class ActivationEvent {
public static final int SERIALIZED_SIZE =
Long.SIZE / Byte.SIZE + // timestamp
Short.SIZE / Byte.SIZE + // serviceName index
+ Short.SIZE / Byte.SIZE + // serviceVersion index
TraceContext.SERIALIZED_LENGTH + // traceContextBuffer
TraceContext.SERIALIZED_LENGTH + // previousContextBuffer
1 + // rootContext
@@ -786,9 +787,14 @@ private static class ActivationEvent {
private static final Map serviceNameMap = new HashMap<>();
private static final Map serviceNameBackMap = new HashMap<>();
+ private static final Map serviceVersionMap = new HashMap<>();
+ private static final Map serviceVersionBackMap = new HashMap<>();
+
private long timestamp;
@Nullable
private String serviceName;
+ @Nullable
+ private String serviceVersion;
private byte[] traceContextBuffer = new byte[TraceContext.SERIALIZED_LENGTH];
private byte[] previousContextBuffer = new byte[TraceContext.SERIALIZED_LENGTH];
private boolean rootContext;
@@ -808,6 +814,7 @@ private void set(TraceContext traceContext, long threadId, boolean activation, @
this.threadId = threadId;
this.activation = activation;
this.serviceName = traceContext.getServiceName();
+ this.serviceVersion = traceContext.getServiceVersion();
if (previousContext != null) {
previousContext.serialize(previousContextBuffer);
rootContext = false;
@@ -845,7 +852,7 @@ private void handleActivationEvent(SamplingProfiler samplingProfiler) {
}
private void startProfiling(SamplingProfiler samplingProfiler) {
- CallTree.Root root = CallTree.createRoot(samplingProfiler.rootPool, traceContextBuffer, serviceName, timestamp);
+ CallTree.Root root = CallTree.createRoot(samplingProfiler.rootPool, traceContextBuffer, serviceName, serviceVersion, timestamp);
if (logger.isDebugEnabled()) {
logger.debug("Create call tree ({}) for thread {}", deserialize(samplingProfiler, traceContextBuffer), threadId);
}
@@ -860,7 +867,7 @@ private void startProfiling(SamplingProfiler samplingProfiler) {
}
private TraceContext deserialize(SamplingProfiler samplingProfiler, byte[] traceContextBuffer) {
- samplingProfiler.contextForLogging.deserialize(traceContextBuffer, null);
+ samplingProfiler.contextForLogging.deserialize(traceContextBuffer, null, null);
return samplingProfiler.contextForLogging;
}
@@ -906,6 +913,7 @@ private void stopProfiling(SamplingProfiler samplingProfiler) {
public void serialize(ByteBuffer buf) {
buf.putLong(timestamp);
buf.putShort(getServiceNameIndex());
+ buf.putShort(getServiceVersionIndex());
buf.put(traceContextBuffer);
buf.put(previousContextBuffer);
buf.put(rootContext ? (byte) 1 : (byte) 0);
@@ -916,6 +924,7 @@ public void serialize(ByteBuffer buf) {
public void deserialize(ByteBuffer buf) {
timestamp = buf.getLong();
serviceName = serviceNameBackMap.get(buf.getShort());
+ serviceVersion = serviceVersionBackMap.get(buf.getShort());
buf.get(traceContextBuffer);
buf.get(previousContextBuffer);
rootContext = buf.get() == 1;
@@ -932,6 +941,16 @@ private short getServiceNameIndex() {
}
return index;
}
+
+ private short getServiceVersionIndex() {
+ Short index = serviceVersionMap.get(serviceVersion);
+ if (index == null) {
+ index = (short) serviceVersionMap.size();
+ serviceVersionMap.put(serviceVersion, index);
+ serviceVersionBackMap.put(index, serviceVersion);
+ }
+ return index;
+ }
}
/**
diff --git a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java
index dbee90ae42..cad43b38b0 100644
--- a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java
+++ b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java
@@ -104,7 +104,7 @@ void testSpanification() throws Exception {
@Test
void testCallTreeWithActiveSpan() {
TraceContext rootContext = CallTreeTest.rootTraceContext(tracer);
- CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), rootContext.serialize(), rootContext.getServiceName(), 0);
+ CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), rootContext.serialize(), rootContext.getServiceName(), rootContext.getServiceVersion(),0);
NoopObjectPool callTreePool = NoopObjectPool.ofRecyclable(CallTree::new);
root.addStackTrace(tracer, List.of(StackFrame.of("A", "a")), 0, callTreePool, 0);
diff --git a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java
index 877590a129..0da789fc61 100644
--- a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java
+++ b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeTest.java
@@ -85,7 +85,7 @@ void tearDown() throws IOException {
@Test
void testCallTree() {
TraceContext traceContext = TraceContext.with64BitId(MockTracer.create());
- CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), traceContext.serialize(), traceContext.getServiceName(), 0);
+ CallTree.Root root = CallTree.createRoot(NoopObjectPool.ofRecyclable(() -> new CallTree.Root(tracer)), traceContext.serialize(), traceContext.getServiceName(), traceContext.getServiceVersion(), 0);
ObjectPool callTreePool = ListBasedObjectPool.ofRecyclable(new ArrayList<>(), Integer.MAX_VALUE, CallTree::new);
root.addStackTrace(tracer, List.of(StackFrame.of("A", "a")), 0, callTreePool, 0);
root.addStackTrace(tracer, List.of(StackFrame.of("A", "b"), StackFrame.of("A", "a")), TimeUnit.MILLISECONDS.toNanos(10), callTreePool, 0);
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java
index f32fa2ccc5..c7af9e0554 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
@@ -90,7 +90,7 @@ public static void determineServiceName(@Nullable String servletContextName, @Nu
serviceName = contextPath.substring(1);
}
if (serviceName != null) {
- GlobalTracer.get().overrideServiceNameForClassLoader(servletContextClassLoader, serviceName);
+ GlobalTracer.get().overrideServiceInfoForClassLoader(servletContextClassLoader, serviceName);
}
}
diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/SpringServiceNameInstrumentation.java b/apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/SpringServiceNameInstrumentation.java
index 1a5d83de73..8ad7cad261 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
@@ -109,7 +109,7 @@ public static void afterInitPropertySources(@Advice.This WebApplicationContext a
}
}
- tracer.overrideServiceNameForClassLoader(classLoader, appName);
+ tracer.overrideServiceInfoForClassLoader(classLoader, appName);
}
}
}
diff --git a/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java b/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java
index 27562e19c8..be66079b97 100644
--- a/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java
+++ b/apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/springwebmvc/template/AbstractViewRenderingInstrumentationTest.java
@@ -82,7 +82,7 @@ void setup() {
@AfterEach
final void cleanUp() {
- tracer.resetServiceNameOverrides();
+ tracer.resetServiceInfoOverrides();
assertThat(tracer.getActive()).isNull();
}
diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java
index e35a40b441..9b6a30c90d 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
@@ -91,7 +91,7 @@ private void executeTest(final AbstractServletContainerIntegrationTest container
/**
* Since we test custom transaction creation through the external plugin, the service name for this transaction cannot be
- * captured through the {@link Tracer#overrideServiceNameForClassLoader(java.lang.ClassLoader, java.lang.String)} mechanism.
+ * captured through the {@link Tracer#overrideServiceInfoForClassLoader(java.lang.ClassLoader, java.lang.String)} mechanism.
*/
@Nullable
@Override
From 5d30f8946a3a808f8897e54980dd906862e56b34 Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Thu, 27 Jan 2022 09:33:01 +0100
Subject: [PATCH 02/46] Added an integration test for retrieving the service
name/version from the main jars manifest (#2190)
---
integration-tests/main-app-test/pom.xml | 51 ++++++++++++++++
.../main/java/co/elastic/apm/test/Main.java | 25 ++++++++
.../java/co/elastic/apm/test/ServiceIT.java | 61 +++++++++++++++++++
integration-tests/pom.xml | 1 +
4 files changed, 138 insertions(+)
create mode 100644 integration-tests/main-app-test/pom.xml
create mode 100644 integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java
create mode 100644 integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.java
diff --git a/integration-tests/main-app-test/pom.xml b/integration-tests/main-app-test/pom.xml
new file mode 100644
index 0000000000..413d55d534
--- /dev/null
+++ b/integration-tests/main-app-test/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ integration-tests
+ co.elastic.apm
+ 1.28.5-SNAPSHOT
+
+
+ main-app-test
+ ${project.groupId}:${project.artifactId}
+
+
+ ${project.basedir}/../..
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ ${version.slf4j}
+ test
+
+
+ org.testcontainers
+ testcontainers
+ ${version.testcontainers}
+ test
+
+
+
+
+ ${project.artifactId}
+
+
+ maven-jar-plugin
+
+
+
+ co.elastic.apm.test.Main
+ My Service Name
+ My Service Version
+
+
+
+
+
+
+
diff --git a/integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java b/integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java
new file mode 100644
index 0000000000..649c71543d
--- /dev/null
+++ b/integration-tests/main-app-test/src/main/java/co/elastic/apm/test/Main.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.test;
+
+public class Main {
+
+ public static void main(String[] args) throws InterruptedException {
+ }
+}
diff --git a/integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.java b/integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.java
new file mode 100644
index 0000000000..36523532d0
--- /dev/null
+++ b/integration-tests/main-app-test/src/test/java/co/elastic/apm/test/ServiceIT.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.test;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Arrays;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ServiceIT {
+
+ @ParameterizedTest
+ @ValueSource(strings = {"openjdk:8", "openjdk:11", "openjdk:17"})
+ void testServiceNameAndVersionFromManifest(String image) {
+ assertThat(new File("target/main-app-test.jar")).exists();
+ GenericContainer> app = new GenericContainer<>(DockerImageName.parse(image))
+ .withFileSystemBind(getAgentJar(), "/tmp/elastic-apm-agent.jar")
+ .withFileSystemBind("target/main-app-test.jar", "/tmp/main-app.jar")
+ .withCommand("java -javaagent:/tmp/elastic-apm-agent.jar -jar /tmp/main-app.jar")
+ .waitingFor(Wait.forLogMessage(".* Starting Elastic APM .*", 1));
+ app.start();
+
+ try {
+ assertThat(app.getLogs()).contains(" as My Service Name (My Service Version) on ");
+ } finally {
+ app.stop();
+ }
+ }
+
+ private static String getAgentJar() {
+ File buildDir = new File("../../elastic-apm-agent/target/");
+ FileFilter fileFilter = file -> file.getName().matches("elastic-apm-agent-\\d\\.\\d+\\.\\d+(\\.RC\\d+)?(-SNAPSHOT)?.jar");
+ return Arrays.stream(buildDir.listFiles(fileFilter))
+ .findFirst()
+ .map(File::getAbsolutePath)
+ .orElseThrow(() -> new IllegalStateException("Agent jar not found. Execute mvn package to build the agent jar."));
+ }
+}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index dd9ff3d4ce..93b67ddad4 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -25,6 +25,7 @@
external-plugin-testruntime-attachjakartaee-jsf-app
+ main-app-test
From 3c3f589111b35d716ef2e5467dddebc0c5bf0edc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 27 Jan 2022 10:15:03 +0100
Subject: [PATCH 03/46] Bump jakarta.jakartaee-api from 9.0.0 to 9.1.0 (#2406)
Bumps jakarta.jakartaee-api from 9.0.0 to 9.1.0.
---
updated-dependencies:
- dependency-name: jakarta.platform:jakarta.jakartaee-api
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml | 2 +-
apm-agent-plugins/apm-jsf-plugin/pom.xml | 2 +-
.../apm-scheduled-annotation-plugin-jakartaee-test/pom.xml | 2 +-
.../jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml b/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml
index 66a7329b7f..d5e16e2336 100644
--- a/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml
+++ b/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml
@@ -18,7 +18,7 @@
jakarta.platformjakarta.jakartaee-api
- 9.0.0
+ 9.1.0test
diff --git a/apm-agent-plugins/apm-jsf-plugin/pom.xml b/apm-agent-plugins/apm-jsf-plugin/pom.xml
index 63b2cdc9b7..44a84bca9f 100644
--- a/apm-agent-plugins/apm-jsf-plugin/pom.xml
+++ b/apm-agent-plugins/apm-jsf-plugin/pom.xml
@@ -25,7 +25,7 @@
jakarta.platformjakarta.jakartaee-api
- 9.0.0
+ 9.1.0provided
diff --git a/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml b/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml
index d9df6ef25b..92da1b7458 100644
--- a/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml
+++ b/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml
@@ -18,7 +18,7 @@
jakarta.platformjakarta.jakartaee-api
- 9.0.0
+ 9.1.0test
diff --git a/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml b/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml
index 478bcec9b7..8e2fc8dd6e 100644
--- a/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml
+++ b/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml
@@ -24,7 +24,7 @@
jakarta.platformjakarta.jakartaee-api
- 9.0.0
+ 9.1.0provided
From be13f5e5da2809dbf21762501d095eb6a5f1e020 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 27 Jan 2022 05:17:41 -0500
Subject: [PATCH 04/46] Bump mockito-core from 4.2.0 to 4.3.1 (#2425)
Bumps [mockito-core](https://github.com/mockito/mockito) from 4.2.0 to 4.3.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.2.0...v4.3.1)
---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 0d4f945b30..f5ba9a30fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -653,7 +653,7 @@
org.mockitomockito-core
- 4.2.0
+ 4.3.1test
From 883786121b5dcdfd7aed68cae0fcc9a4a1ca9e3c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 27 Jan 2022 06:33:25 -0500
Subject: [PATCH 05/46] Bump jakarta.servlet.jsp-api from 2.3.6 to 3.0.0
(#2405)
* Bump jakarta.servlet.jsp-api from 2.3.6 to 3.0.0
Bumps [jakarta.servlet.jsp-api](https://github.com/eclipse-ee4j/jsp-api) from 2.3.6 to 3.0.0.
- [Release notes](https://github.com/eclipse-ee4j/jsp-api/releases)
- [Commits](https://github.com/eclipse-ee4j/jsp-api/commits)
---
updated-dependencies:
- dependency-name: jakarta.servlet.jsp:jakarta.servlet.jsp-api
dependency-type: direct:development
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
* keep older version for struts plugin test
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sylvain Juge
From c8601026db1b785866f47a0803832d9f1a76103e Mon Sep 17 00:00:00 2001
From: SylvainJuge
Date: Thu, 27 Jan 2022 12:34:19 +0100
Subject: [PATCH 06/46] Avoid eager initialization of JMX on Weblogic (#2420)
* do not create platform mbean server on WL
* add 'weblogic.home' in the heuristic for safety
* update changelog
---
CHANGELOG.asciidoc | 1 +
.../apm/agent/jmx/JmxMetricTracker.java | 22 +++++++++++--------
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index c817ca98b5..220e2a953b 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -45,6 +45,7 @@ paths. The BCI warmup is on by default and may be disabled through the internal
** Dubbo transaction will should be created at the provider side
** APM headers conversion issue within dubbo transaction
* Fix External plugins automatic setting of span outcome - {pull}2376[#2376]
+* Avoid early initialization of JMX on Weblogic - {pull}2420[#2420]
[[release-notes-1.x]]
=== Java Agent version 1.x
diff --git a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java
index 8e5c60d19a..52330e467f 100644
--- a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java
+++ b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java
@@ -94,10 +94,16 @@ private synchronized void tryInit() {
if (this.server != null || this.logManagerPropertyPoller != null) {
return;
}
- // Avoid creating the platform MBean server, only get it if already initialized
- // otherwise WildFly fails to start with a IllegalStateException:
+ // Do not eagerly trigger creation of the platform MBean server for known problematic cases
+ //
+ // WildFly fails to start with a IllegalStateException:
// WFLYLOG0078: The logging subsystem requires the log manager to be org.jboss.logmanager.LogManager
- if (setsCustomLogManager()) {
+ //
+ // JBoss sets the 'javax.management.builder.initial' system property, but uses the module classloader and
+ // current thread context class loader to initialize it
+ //
+ // Weblogic sets the 'javax.management.builder.initial' system property at runtime
+ if (setCustomPlatformMBeanServer()) {
List servers = MBeanServerFactory.findMBeanServer(null);
if (!servers.isEmpty()) {
// platform MBean server is already initialized
@@ -120,7 +126,7 @@ private MBeanServer getPlatformMBeanServerThreadSafely() {
}
private void deferInit() {
- logger.debug("Deferring initialization of JMX metric tracking until log manager is initialized");
+ logger.debug("Deferring initialization of JMX metric tracking until platform mbean server is initialized");
Thread thread = new Thread(new Runnable() {
private final long timeout = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5);
@@ -131,9 +137,6 @@ public void run() {
List servers = MBeanServerFactory.findMBeanServer(null);
if (!servers.isEmpty()) {
// avoid actively creating a platform mbean server
- // because JBoss sets the javax.management.builder.initial system property
- // this makes Java create a JBoss specific mbean server that can only be loaded
- // when the module class loader is set as the current thread's context class loader
init(servers.get(0));
return;
}
@@ -151,8 +154,9 @@ public void run() {
logManagerPropertyPoller = thread;
}
- private boolean setsCustomLogManager() {
- return ClassLoader.getSystemClassLoader().getResource("org/jboss/modules/Main.class") != null;
+ private boolean setCustomPlatformMBeanServer() {
+ return ClassLoader.getSystemClassLoader().getResource("org/jboss/modules/Main.class") != null
+ || System.getProperty("weblogic.Name") != null || System.getProperty("weblogic.home") != null;
}
synchronized void init(final MBeanServer platformMBeanServer) {
From c4d6148ca0a80d680c3c36ca6f9dee76ca90e476 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 27 Jan 2022 06:54:37 -0500
Subject: [PATCH 07/46] Bump json-schema-validator from 1.0.65 to 1.0.66
(#2426)
Bumps [json-schema-validator](https://github.com/networknt/json-schema-validator) from 1.0.65 to 1.0.66.
- [Release notes](https://github.com/networknt/json-schema-validator/releases)
- [Changelog](https://github.com/networknt/json-schema-validator/blob/master/CHANGELOG.md)
- [Commits](https://github.com/networknt/json-schema-validator/compare/1.0.65...1.0.66)
---
updated-dependencies:
- dependency-name: com.networknt:json-schema-validator
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index f5ba9a30fa..4face81552 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,7 +117,7 @@
5.0.15.RELEASE2.2.2.RELEASE9.4.11.v20180605
- 1.0.65
+ 1.0.661.12.79.2
From 496388ab560f2eaf17717807d1a989319fae46e6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 27 Jan 2022 08:14:54 -0500
Subject: [PATCH 08/46] Bump version.slf4j from 1.7.32 to 1.7.35 (#2427)
Bumps `version.slf4j` from 1.7.32 to 1.7.35.
Updates `slf4j-api` from 1.7.32 to 1.7.35
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.32...v_1.7.35)
Updates `slf4j-simple` from 2.0.0-alpha0 to 1.7.35
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/commits/v_1.7.35)
---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
dependency-type: direct:production
update-type: version-update:semver-patch
- dependency-name: org.slf4j:slf4j-simple
dependency-type: direct:development
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
apm-agent-plugins/apm-servlet-jakarta-test/pom.xml | 4 ++--
.../plugin-instrumentation-target/pom.xml | 2 +-
pom.xml | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml b/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml
index 0bef53e177..848bd63e5b 100644
--- a/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml
+++ b/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml
@@ -62,12 +62,12 @@
org.slf4jslf4j-api
- 2.0.0-alpha0
+ 1.7.35org.slf4jslf4j-simple
- 2.0.0-alpha0
+ 1.7.35
diff --git a/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml b/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml
index cfae15aa1c..6a8954da15 100644
--- a/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml
+++ b/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml
@@ -20,7 +20,7 @@
org.slf4jslf4j-api
- 1.7.25
+ 1.7.35
diff --git a/pom.xml b/pom.xml
index 4face81552..34bc3e5aee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -108,7 +108,7 @@
5.8.21.2.34.9.1
- 1.7.32
+ 1.7.35
From b7604c0f6d1f2cac0457ff133cfb6c6e25c288ba Mon Sep 17 00:00:00 2001
From: jackshirazi
Date: Thu, 27 Jan 2022 14:03:54 +0000
Subject: [PATCH 09/46] add jakartaee9 support notes (#2318)
* add jakartaee9 support notes
* apply suggested changes
---
docs/supported-technologies.asciidoc | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc
index 8e23da8836..bee5f30a2c 100644
--- a/docs/supported-technologies.asciidoc
+++ b/docs/supported-technologies.asciidoc
@@ -93,7 +93,7 @@ the JVM arguments.
Starting in version 1.18.0, additional spans are created if the servlet dispatches execution to another servlet through the `forward` or
`include` APIs, or to an error page.
See also <>
-|1.0.0
+|1.0.0, 4.0+ (`jakarta.servlet`) since 1.28.0
|Spring Web MVC
|4.x, 5.x
@@ -111,7 +111,7 @@ the JVM arguments.
|2.2.x, 2.3.x, 3.0.x
|If you are using JSF, transactions are named based on the requested Facelets and spans are captured for visibility into execution and
rendering
-|1.0.0
+|1.0.0, `jakarta.faces` since 1.28.0
|Spring Boot
|1.5+, 2.x
@@ -119,7 +119,7 @@ rendering
|1.0.0
|JAX-RS
-|2.x
+|2.x, 3.x
|The transactions are named based on your resources (`ResourceClass#resourceMethod`).
Note that only the packages configured in <> are scanned for JAX-RS resources.
If you don't set this option,
@@ -127,7 +127,7 @@ rendering
This comes at the cost of increased startup times, however.
Note: JAX-RS is only supported when running on a supported <>.
-|1.0.0
+|1.0.0, `jakarta.ws.rs` since 1.28.0
|JAX-WS
|
@@ -138,7 +138,7 @@ rendering
This comes at the cost of increased startup times, however.
Note: JAX-WS is only supported when running on a supported <> and when using the HTTP binding.
-|1.4.0
+|1.4.0, `jakarta.jws` since 1.28.0
|Grails
|3+
@@ -398,7 +398,7 @@ same trace.
side, the agent reads the context from the Message property through `javax.jms.MessageConsumer#receive`,
`javax.jms.MessageConsumer#receiveNoWait`, `javax.jms.JMSConsumer#receive`, `javax.jms.JMSConsumer#receiveNoWait` or
`javax.jms.MessageListener#onMessage` and uses it for enabling distributed tracing.
-|Enabled by default since 1.13.0, added as an experimental plugin in 1.7.0
+|Enabled by default since 1.13.0, added as an experimental plugin in 1.7.0, `jakarta.jms` since 1.28.0
|Kafka
| <0.11.0 - without distributed tracing; 0.11.0+ - full support
@@ -449,7 +449,7 @@ When using a scheduling framework a transaction for every execution will be crea
`javax.ejb.Schedules`
`jakarta.ejb.Schedule`
`jakarta.ejb.Schedules` in order to create a transaction with the type `scheduled`, representing the scheduled task execution
-|1.6.0
+|1.6.0, `jakarta.ejb.Schedule` since 1.28.0
|Quartz
|1.0+
From a2927923dbed928c1c8010eacf526aaedc8ae929 Mon Sep 17 00:00:00 2001
From: Victor Martinez
Date: Thu, 27 Jan 2022 14:39:26 +0000
Subject: [PATCH 10/46] ci: enable optional stages in the branch (#2422)
---
Jenkinsfile | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 4862f338b5..27d5c4166f 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -177,6 +177,7 @@ pipeline {
expression { return env.GITHUB_COMMENT?.contains('integration tests') }
expression { matchesPrLabel(label: 'ci:agent-integration') }
expression { return env.CHANGE_ID != null && !pullRequest.draft }
+ not { changeRequest() }
}
}
steps {
@@ -206,6 +207,7 @@ pipeline {
expression { return env.GITHUB_COMMENT?.contains('integration tests') }
expression { matchesPrLabel(label: 'ci:agent-integration') }
expression { return env.CHANGE_ID != null && !pullRequest.draft }
+ not { changeRequest() }
}
}
steps {
@@ -237,11 +239,9 @@ pipeline {
}
when {
beforeAgent true
- allOf {
- anyOf {
- branch 'main'
- expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
- }
+ anyOf {
+ branch 'main'
+ expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
expression { return params.bench_ci }
}
}
@@ -271,10 +271,6 @@ pipeline {
stage('Javadoc') {
agent { label 'linux && immutable' }
options { skipDefaultCheckout() }
- when {
- beforeAgent true
- expression { return env.ONLY_DOCS == "false" }
- }
steps {
withGithubNotify(context: 'Javadoc') {
deleteDir()
@@ -302,6 +298,7 @@ pipeline {
expression { return env.GITHUB_COMMENT?.contains('end-to-end tests') }
expression { matchesPrLabel(label: 'ci:end-to-end') }
expression { return env.CHANGE_ID != null && !pullRequest.draft }
+ not { changeRequest() }
}
}
}
From 84e8f8b89182b041d6f52c8f09d83eee607d88af Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Fri, 28 Jan 2022 10:58:57 +0100
Subject: [PATCH 11/46] Support for the Java LDAP client (#2355)
---
CHANGELOG.asciidoc | 1 +
.../test/resources/json-specs/span_types.json | 6 +
.../apm-java-ldap-plugin/pom.xml | 28 ++++
.../apm/agent/java_ldap/LdapClientAdvice.java | 71 +++++++++
.../java_ldap/LdapClientInstrumentation.java | 59 ++++++++
...ic.apm.agent.sdk.ElasticApmInstrumentation | 1 +
.../agent/java_ldap/LdapClientAdviceTest.java | 139 ++++++++++++++++++
.../src/test/resources/test.ldif | 24 +++
apm-agent-plugins/pom.xml | 1 +
apm-agent/pom.xml | 5 +
docs/configuration.asciidoc | 8 +-
docs/supported-technologies.asciidoc | 4 +
12 files changed, 343 insertions(+), 4 deletions(-)
create mode 100644 apm-agent-plugins/apm-java-ldap-plugin/pom.xml
create mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java
create mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java
create mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
create mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java
create mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 220e2a953b..a84ad5b5a0 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -33,6 +33,7 @@ endif::[]
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]
+* Support for the Java LDAP client - {pull}2355[#2355]
[float]
===== Bug fixes
diff --git a/apm-agent-core/src/test/resources/json-specs/span_types.json b/apm-agent-core/src/test/resources/json-specs/span_types.json
index 193489a7a7..06a5e6be34 100644
--- a/apm-agent-core/src/test/resources/json-specs/span_types.json
+++ b/apm-agent-core/src/test/resources/json-specs/span_types.json
@@ -225,6 +225,12 @@
"ruby",
"java"
]
+ },
+ "ldap": {
+ "__description": "LDAP client",
+ "__used_by": [
+ "java"
+ ]
}
}
},
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
new file mode 100644
index 0000000000..b95665785c
--- /dev/null
+++ b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+
+ apm-agent-plugins
+ co.elastic.apm
+ 1.28.5-SNAPSHOT
+
+
+ apm-java-ldap-plugin
+ ${project.groupId}:${project.artifactId}
+
+
+ ${project.basedir}/../..
+ true
+
+
+
+
+ com.unboundid
+ unboundid-ldapsdk
+ 6.0.3
+ test
+
+
+
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java b/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java
new file mode 100644
index 0000000000..c092cb24aa
--- /dev/null
+++ b/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java
@@ -0,0 +1,71 @@
+/*
+ * 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.java_ldap;
+
+import co.elastic.apm.agent.impl.ElasticApmTracer;
+import co.elastic.apm.agent.impl.GlobalTracer;
+import co.elastic.apm.agent.impl.transaction.AbstractSpan;
+import co.elastic.apm.agent.impl.transaction.Outcome;
+import co.elastic.apm.agent.impl.transaction.Span;
+import com.sun.jndi.ldap.Connection;
+import com.sun.jndi.ldap.LdapResult;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import javax.annotation.Nullable;
+
+public class LdapClientAdvice {
+
+ private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl();
+
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object onEnter(@Advice.Origin("#m") String methodName, @Advice.FieldValue(value = "conn", typing = Assigner.Typing.DYNAMIC) Connection connection) {
+ AbstractSpan> parent = tracer.getActive();
+ if (parent == null) {
+ return null;
+ }
+
+ Span span = parent.createExitSpan();
+ if (span == null) {
+ return null;
+ }
+
+ span.appendToName("LDAP ").appendToName(methodName)
+ .withType("external")
+ .withSubtype("ldap");
+
+ if (connection != null) {
+ span.getContext()
+ .getDestination().withAddress(connection.host).withPort(connection.port)
+ .getService().getResource().append(connection.host).append(":").append(connection.port);
+ }
+
+ return span.activate();
+ }
+
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onExit(@Advice.Enter @Nullable Object spanObj, @Nullable @Advice.Return LdapResult ldapResult, @Nullable @Advice.Thrown Throwable t) {
+ Span span = (Span) spanObj;
+ if (span != null) {
+ span.withOutcome((ldapResult != null && ldapResult.status == 0 /* LDAP_SUCCESS */) ? Outcome.SUCCESS : Outcome.FAILURE)
+ .captureException(t)
+ .deactivate().end();
+ }
+ }
+}
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java b/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java
new file mode 100644
index 0000000000..edf5bec64d
--- /dev/null
+++ b/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java
@@ -0,0 +1,59 @@
+/*
+ * 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.java_ldap;
+
+import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class LdapClientInstrumentation extends TracerAwareInstrumentation {
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named("com.sun.jndi.ldap.LdapClient");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return named("authenticate")
+ .or(named("add"))
+ .or(named("compare"))
+ .or(named("delete"))
+ .or(named("extendedOp"))
+ .or(named("moddn"))
+ .or(named("modify"))
+ .or(named("search"));
+ }
+
+ @Override
+ public String getAdviceClassName() {
+ return "co.elastic.apm.agent.java_ldap.LdapClientAdvice";
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("java-ldap");
+ }
+}
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
new file mode 100644
index 0000000000..4f5d5f9bf4
--- /dev/null
+++ b/apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
@@ -0,0 +1 @@
+co.elastic.apm.agent.java_ldap.LdapClientInstrumentation
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java b/apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java
new file mode 100644
index 0000000000..b6872f740d
--- /dev/null
+++ b/apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.java_ldap;
+
+import co.elastic.apm.agent.AbstractInstrumentationTest;
+import co.elastic.apm.agent.impl.transaction.Outcome;
+import co.elastic.apm.agent.impl.transaction.Span;
+import co.elastic.apm.agent.impl.transaction.Transaction;
+import co.elastic.apm.agent.testutils.TestPort;
+import com.unboundid.ldap.listener.InMemoryDirectoryServer;
+import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
+import com.unboundid.ldap.listener.InMemoryListenerConfig;
+import com.unboundid.ldif.LDIFReader;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.naming.Context;
+import javax.naming.directory.InitialDirContext;
+import java.util.Hashtable;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapClientAdviceTest extends AbstractInstrumentationTest {
+
+ private static InMemoryDirectoryServer ldapServer;
+
+ @BeforeAll
+ static void startServer() throws Exception {
+ InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
+ config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("test", TestPort.getAvailableRandomPort()));
+
+ ldapServer = new InMemoryDirectoryServer(config);
+ ldapServer.importFromLDIF(true, new LDIFReader(LdapClientAdviceTest.class.getResourceAsStream("/test.ldif")));
+ ldapServer.startListening();
+ }
+
+ @AfterAll
+ static void stopServer() {
+ ldapServer.shutDown(true);
+ }
+
+ @Test
+ void testSuccessfulAuthentication() throws Exception {
+ Hashtable environment = getEnvironment();
+
+ Transaction transaction = startTestRootTransaction();
+ try {
+ new InitialDirContext(environment).close();
+ } catch (Exception ignored) {
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ List spans = reporter.getSpans();
+ assertThat(spans.size()).isEqualTo(1);
+
+ assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
+ }
+
+ @Test
+ void testUnsuccessfulAuthentication() {
+ Hashtable environment = getEnvironment();
+ environment.put(Context.SECURITY_CREDENTIALS, "wrong password");
+
+ Transaction transaction = startTestRootTransaction();
+ try {
+ new InitialDirContext(environment).close();
+ } catch (Exception ignored) {
+ ignored.printStackTrace();
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ List spans = reporter.getSpans();
+ assertThat(spans.size()).isEqualTo(1);
+
+ assertSpan(spans.get(0), "authenticate", Outcome.FAILURE);
+ }
+
+ @Test
+ void testSearch() {
+ Hashtable environment = getEnvironment();
+
+ Transaction transaction = startTestRootTransaction();
+ try {
+ InitialDirContext context = new InitialDirContext(environment);
+ context.search("dc=example,dc=com", "(&(objectClass=person)(uid=tobiasstadler))", null);
+ context.close();
+ } catch (Exception ignored) {
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ List spans = reporter.getSpans();
+ assertThat(spans.size()).isEqualTo(2);
+
+ assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
+ assertSpan(spans.get(1), "search", Outcome.SUCCESS);
+ }
+
+ private static Hashtable getEnvironment() {
+ Hashtable environment = new Hashtable<>();
+
+ environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, "ldap://localhost:" + ldapServer.getListenPort());
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, "cn=Tobias Stadler,ou=Users,dc=example,dc=com");
+ environment.put(Context.SECURITY_CREDENTIALS, "123456");
+
+ return environment;
+ }
+
+ static void assertSpan(Span span, String method, Outcome outcome) {
+ assertThat(span.getNameAsString()).isEqualTo("LDAP " + method);
+ assertThat(span.getType()).isEqualTo("external");
+ assertThat(span.getSubtype()).isEqualTo("ldap");
+ assertThat(span.getOutcome()).isEqualTo(outcome);
+ assertThat(span.getContext().getDestination().getAddress().toString()).isEqualTo("localhost");
+ assertThat(span.getContext().getDestination().getPort()).isEqualTo(ldapServer.getListenPort());
+ }
+}
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif b/apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif
new file mode 100644
index 0000000000..01bf3ed14c
--- /dev/null
+++ b/apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif
@@ -0,0 +1,24 @@
+dn: dc=example,dc=com
+objectClass: domain
+objectClass: top
+dc: example
+
+dn: ou=Users,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: Users
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: Groups
+
+dn: cn=Tobias Stadler,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: Tobias Stadler
+sn: Stadler
+uid: tobiasstadler
+userPassword: 123456
diff --git a/apm-agent-plugins/pom.xml b/apm-agent-plugins/pom.xml
index 2319871c24..ccffba1871 100644
--- a/apm-agent-plugins/pom.xml
+++ b/apm-agent-plugins/pom.xml
@@ -69,6 +69,7 @@
apm-jaxws-plugin-jakartaee-testapm-jaxrs-plugin-jakartaee-testapm-scheduled-annotation-plugin-jakartaee-test
+ apm-java-ldap-plugin
diff --git a/apm-agent/pom.xml b/apm-agent/pom.xml
index 6c35eb77f6..332acb9b7a 100644
--- a/apm-agent/pom.xml
+++ b/apm-agent/pom.xml
@@ -316,6 +316,11 @@
apm-awslambda-plugin${project.version}
+
+ ${project.groupId}
+ apm-java-ldap-plugin
+ ${project.version}
+
diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc
index 0ad9bfefbf..9f5c8b2506 100644
--- a/docs/configuration.asciidoc
+++ b/docs/configuration.asciidoc
@@ -718,7 +718,7 @@ you should add an additional entry to this list (make sure to also include the d
==== `enable_instrumentations` (added[1.28.0])
A list of instrumentations which should be selectively enabled.
-Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>.
When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>.
@@ -746,7 +746,7 @@ NOTE: Changing this value at runtime can slow down the application temporarily.
==== `disable_instrumentations` (added[1.0.0,Changing this value at runtime is possible since version 1.15.0])
A list of instrumentations which should be disabled.
-Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
For version `1.25.0` and later, use <> to enable experimental instrumentations.
NOTE: Changing this value at runtime can slow down the application temporarily.
@@ -2999,7 +2999,7 @@ The default unit for this option is `ms`.
# sanitize_field_names=password,passwd,pwd,secret,*key,*token*,*session*,*credit*,*card*,*auth*,set-cookie
# A list of instrumentations which should be selectively enabled.
-# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
# When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>.
# When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>.
#
@@ -3012,7 +3012,7 @@ The default unit for this option is `ms`.
# enable_instrumentations=
# A list of instrumentations which should be disabled.
-# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
# For version `1.25.0` and later, use <> to enable experimental instrumentations.
#
# NOTE: Changing this value at runtime can slow down the application temporarily.
diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc
index bee5f30a2c..0c5d7bb934 100644
--- a/docs/supported-technologies.asciidoc
+++ b/docs/supported-technologies.asciidoc
@@ -336,6 +336,10 @@ The spans are named after the schema ``, for example `GET elastic
|
| 1.25.0
+|LdapClient
+|
+|
+| 1.29.0
|===
From dd23b58d82233cfd0a02f9422342f7fbfb5979a0 Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Fri, 28 Jan 2022 12:25:36 +0100
Subject: [PATCH 12/46] ConfigurationExporterTest is in the apm-agent module
(#2294)
---
.../apm/agent/configuration/ConfigurationExporterTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java b/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java
index 12aecc7897..ce1ec0d087 100644
--- a/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java
+++ b/apm-agent/src/test/java/co/elastic/apm/agent/configuration/ConfigurationExporterTest.java
@@ -111,7 +111,7 @@ void testGeneratedConfigurationDocsAreUpToDate() throws IOException, TemplateExc
assertThat(renderedDocumentation)
.withFailMessage("The rendered configuration documentation (/docs/configuration.asciidoc) is not up-to-date.\n" +
"If you see this error on CI, it means you have to execute the tests locally " +
- "(./mvnw clean test -pl elastic-apm-agent -am -DfailIfNoTests=false -Dtest=ConfigurationExporterTest) " +
+ "(./mvnw clean test -pl apm-agent -am -DfailIfNoTests=false -Dtest=ConfigurationExporterTest) " +
"which will update the rendered docs.\n" +
"If you see this error while running the tests locally, there's nothing more to do - the rendered docs have been updated. " +
"When you execute this test the next time, it will not fail anymore.")
From c6d2f551c1e3a94a8e1502b12083115082756d7e Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Fri, 28 Jan 2022 14:43:31 +0100
Subject: [PATCH 13/46] Support for instrumenting Struts 2 static resource
requests (#1949)
---
CHANGELOG.asciidoc | 1 +
apm-agent-plugins/apm-struts-plugin/pom.xml | 2 +-
...NameAdvice.java => ActionProxyAdvice.java} | 9 +---
...n.java => ActionProxyInstrumentation.java} | 13 +----
.../agent/struts/ExecuteOperationsAdvice.java | 48 +++++++++++++++++++
.../ExecuteOperationsInstrumentation.java | 43 +++++++++++++++++
.../agent/struts/StrutsFrameworkUtils.java | 31 ++++++++++++
.../agent/struts/StrutsInstrumentation.java | 33 +++++++++++++
...ic.apm.agent.sdk.ElasticApmInstrumentation | 3 +-
...onTest.java => ActionProxyAdviceTest.java} | 2 +-
.../src/test/resources/struts.xml | 4 +-
11 files changed, 166 insertions(+), 23 deletions(-)
rename apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/{Struts2TransactionNameAdvice.java => ActionProxyAdvice.java} (83%)
rename apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/{Struts2TransactionNameInstrumentation.java => ActionProxyInstrumentation.java} (80%)
create mode 100644 apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java
create mode 100644 apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java
create mode 100644 apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java
create mode 100644 apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java
rename apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/{Struts2TransactionNameInstrumentationTest.java => ActionProxyAdviceTest.java} (96%)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index a84ad5b5a0..e0c303f4ca 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -34,6 +34,7 @@ endif::[]
* 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]
* Support for the Java LDAP client - {pull}2355[#2355]
+* Added support for instrumenting Struts 2 static resource requests - {pull}1949[#1949]
[float]
===== Bug fixes
diff --git a/apm-agent-plugins/apm-struts-plugin/pom.xml b/apm-agent-plugins/apm-struts-plugin/pom.xml
index 1bead19fa1..60b65ce1c9 100644
--- a/apm-agent-plugins/apm-struts-plugin/pom.xml
+++ b/apm-agent-plugins/apm-struts-plugin/pom.xml
@@ -37,7 +37,7 @@
javax.servletjavax.servlet-api3.1.0
- test
+ provided
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameAdvice.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyAdvice.java
similarity index 83%
rename from apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameAdvice.java
rename to apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyAdvice.java
index 59c9fdb1a1..62be176ac1 100644
--- a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameAdvice.java
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyAdvice.java
@@ -21,15 +21,12 @@
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.util.TransactionNameUtils;
-import co.elastic.apm.agent.util.VersionUtils;
import com.opensymphony.xwork2.ActionProxy;
import net.bytebuddy.asm.Advice;
import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_HIGH_LEVEL_FRAMEWORK;
-public class Struts2TransactionNameAdvice {
-
- private static final String FRAMEWORK_NAME = "Struts";
+public class ActionProxyAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setTransactionName(@Advice.This ActionProxy actionProxy) {
@@ -38,9 +35,7 @@ public static void setTransactionName(@Advice.This ActionProxy actionProxy) {
return;
}
- transaction.setFrameworkName(FRAMEWORK_NAME);
- transaction.setFrameworkVersion(VersionUtils.getVersion(ActionProxy.class, "org.apache.struts", "struts2-core"));
-
TransactionNameUtils.setNameFromClassAndMethod(actionProxy.getAction().getClass().getSimpleName(), actionProxy.getMethod(), transaction.getAndOverrideName(PRIO_HIGH_LEVEL_FRAMEWORK));
+ StrutsFrameworkUtils.setFrameworkNameAndVersion(transaction);
}
}
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentation.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyInstrumentation.java
similarity index 80%
rename from apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentation.java
rename to apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyInstrumentation.java
index 7e941d23b4..de93074ec7 100644
--- a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentation.java
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ActionProxyInstrumentation.java
@@ -18,20 +18,16 @@
*/
package co.elastic.apm.agent.struts;
-import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
-import java.util.Collection;
-
-import static java.util.Collections.singletonList;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.named;
-public class Struts2TransactionNameInstrumentation extends TracerAwareInstrumentation {
+public class ActionProxyInstrumentation extends StrutsInstrumentation {
@Override
public ElementMatcher super NamedElement> getTypeMatcherPreFilter() {
@@ -48,13 +44,8 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
return named("execute");
}
- @Override
- public Collection getInstrumentationGroupNames() {
- return singletonList("struts");
- }
-
@Override
public String getAdviceClassName() {
- return "co.elastic.apm.agent.struts.Struts2TransactionNameAdvice";
+ return "co.elastic.apm.agent.struts.ActionProxyAdvice";
}
}
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java
new file mode 100644
index 0000000000..304b39237e
--- /dev/null
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsAdvice.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.struts;
+
+import co.elastic.apm.agent.impl.GlobalTracer;
+import co.elastic.apm.agent.impl.context.web.WebConfiguration;
+import co.elastic.apm.agent.impl.transaction.Transaction;
+import co.elastic.apm.agent.util.TransactionNameUtils;
+import net.bytebuddy.asm.Advice;
+
+import javax.servlet.http.HttpServletRequest;
+
+import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_HIGH_LEVEL_FRAMEWORK;
+
+public class ExecuteOperationsAdvice {
+
+ private static final WebConfiguration webConfig = GlobalTracer.requireTracerImpl().getConfig(WebConfiguration.class);
+
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static void setTransactionName(@Advice.Argument(0) HttpServletRequest request, @Advice.Return boolean handled) {
+ Transaction transaction = GlobalTracer.get().currentTransaction();
+ if (!handled || transaction == null) {
+ return;
+ }
+
+ StringBuilder transactionName = transaction.getAndOverrideName(PRIO_HIGH_LEVEL_FRAMEWORK);
+ if (transactionName != null) {
+ TransactionNameUtils.setNameFromHttpRequestPath(request.getMethod(), request.getServletPath(), transactionName, webConfig.getUrlGroups());
+ StrutsFrameworkUtils.setFrameworkNameAndVersion(transaction);
+ }
+ }
+}
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java
new file mode 100644
index 0000000000..e858289c41
--- /dev/null
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/ExecuteOperationsInstrumentation.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.struts;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class ExecuteOperationsInstrumentation extends StrutsInstrumentation {
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named("org.apache.struts2.dispatcher.ExecuteOperations");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return named("executeStaticResourceRequest");
+ }
+
+ @Override
+ public String getAdviceClassName() {
+ return "co.elastic.apm.agent.struts.ExecuteOperationsAdvice";
+ }
+}
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java
new file mode 100644
index 0000000000..12ee4223fb
--- /dev/null
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsFrameworkUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.struts;
+
+import co.elastic.apm.agent.impl.transaction.Transaction;
+import co.elastic.apm.agent.util.VersionUtils;
+import com.opensymphony.xwork2.ActionProxy;
+
+public class StrutsFrameworkUtils {
+
+ public static void setFrameworkNameAndVersion(Transaction transaction) {
+ transaction.setFrameworkName("Struts");
+ transaction.setFrameworkVersion(VersionUtils.getVersion(ActionProxy.class, "org.apache.struts", "struts2-core"));
+ }
+}
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java
new file mode 100644
index 0000000000..a86c9b86e7
--- /dev/null
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/java/co/elastic/apm/agent/struts/StrutsInstrumentation.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.struts;
+
+import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
+
+import java.util.Collection;
+
+import static java.util.Collections.singletonList;
+
+abstract class StrutsInstrumentation extends TracerAwareInstrumentation {
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return singletonList("struts");
+ }
+}
diff --git a/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
index 5bc10dbdab..81a0223cef 100644
--- a/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
+++ b/apm-agent-plugins/apm-struts-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
@@ -1 +1,2 @@
-co.elastic.apm.agent.struts.Struts2TransactionNameInstrumentation
+co.elastic.apm.agent.struts.ActionProxyInstrumentation
+co.elastic.apm.agent.struts.ExecuteOperationsInstrumentation
diff --git a/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentationTest.java b/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/ActionProxyAdviceTest.java
similarity index 96%
rename from apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentationTest.java
rename to apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/ActionProxyAdviceTest.java
index 4089fd2019..c16f3cbd62 100644
--- a/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/Struts2TransactionNameInstrumentationTest.java
+++ b/apm-agent-plugins/apm-struts-plugin/src/test/java/co/elastic/apm/agent/struts/ActionProxyAdviceTest.java
@@ -28,7 +28,7 @@
import static org.assertj.core.api.Assertions.assertThat;
-public class Struts2TransactionNameInstrumentationTest extends StrutsTestCase {
+public class ActionProxyAdviceTest extends StrutsTestCase {
public static class TestAction extends ActionSupport {
diff --git a/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml b/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml
index da233b0bae..701417cdd9 100644
--- a/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml
+++ b/apm-agent-plugins/apm-struts-plugin/src/test/resources/struts.xml
@@ -2,12 +2,12 @@
-
+
200
-
+
200
From 4d310989c8b26526766904c42b1ab3faae3d2201 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 28 Jan 2022 14:49:45 +0100
Subject: [PATCH 14/46] Band-aid fix for compile error in IntelliJ in ldap
plugin
---
apm-agent-plugins/apm-java-ldap-plugin/pom.xml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
index b95665785c..8c2481b68b 100644
--- a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
+++ b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
@@ -15,6 +15,9 @@
${project.basedir}/../..true
+
+ 1.7
+ 1.7
From cdca2a233e9cee8837cdd4dfa12f8b8a7e84dd61 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 28 Jan 2022 15:38:48 +0100
Subject: [PATCH 15/46] Remove ldap plugin (#2431)
---
CHANGELOG.asciidoc | 1 -
.../test/resources/json-specs/span_types.json | 6 -
.../apm-java-ldap-plugin/pom.xml | 31 ----
.../apm/agent/java_ldap/LdapClientAdvice.java | 71 ---------
.../java_ldap/LdapClientInstrumentation.java | 59 --------
...ic.apm.agent.sdk.ElasticApmInstrumentation | 1 -
.../agent/java_ldap/LdapClientAdviceTest.java | 139 ------------------
.../src/test/resources/test.ldif | 24 ---
apm-agent-plugins/pom.xml | 1 -
apm-agent/pom.xml | 5 -
docs/configuration.asciidoc | 8 +-
docs/supported-technologies.asciidoc | 4 -
12 files changed, 4 insertions(+), 346 deletions(-)
delete mode 100644 apm-agent-plugins/apm-java-ldap-plugin/pom.xml
delete mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java
delete mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java
delete mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
delete mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java
delete mode 100644 apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index e0c303f4ca..12ad7b3f91 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -33,7 +33,6 @@ endif::[]
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]
-* Support for the Java LDAP client - {pull}2355[#2355]
* Added support for instrumenting Struts 2 static resource requests - {pull}1949[#1949]
[float]
diff --git a/apm-agent-core/src/test/resources/json-specs/span_types.json b/apm-agent-core/src/test/resources/json-specs/span_types.json
index 06a5e6be34..193489a7a7 100644
--- a/apm-agent-core/src/test/resources/json-specs/span_types.json
+++ b/apm-agent-core/src/test/resources/json-specs/span_types.json
@@ -225,12 +225,6 @@
"ruby",
"java"
]
- },
- "ldap": {
- "__description": "LDAP client",
- "__used_by": [
- "java"
- ]
}
}
},
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
deleted file mode 100644
index 8c2481b68b..0000000000
--- a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
- 4.0.0
-
-
- apm-agent-plugins
- co.elastic.apm
- 1.28.5-SNAPSHOT
-
-
- apm-java-ldap-plugin
- ${project.groupId}:${project.artifactId}
-
-
- ${project.basedir}/../..
- true
-
- 1.7
- 1.7
-
-
-
-
- com.unboundid
- unboundid-ldapsdk
- 6.0.3
- test
-
-
-
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java b/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.java
deleted file mode 100644
index c092cb24aa..0000000000
--- a/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientAdvice.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.java_ldap;
-
-import co.elastic.apm.agent.impl.ElasticApmTracer;
-import co.elastic.apm.agent.impl.GlobalTracer;
-import co.elastic.apm.agent.impl.transaction.AbstractSpan;
-import co.elastic.apm.agent.impl.transaction.Outcome;
-import co.elastic.apm.agent.impl.transaction.Span;
-import com.sun.jndi.ldap.Connection;
-import com.sun.jndi.ldap.LdapResult;
-import net.bytebuddy.asm.Advice;
-import net.bytebuddy.implementation.bytecode.assign.Assigner;
-
-import javax.annotation.Nullable;
-
-public class LdapClientAdvice {
-
- private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl();
-
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
- public static Object onEnter(@Advice.Origin("#m") String methodName, @Advice.FieldValue(value = "conn", typing = Assigner.Typing.DYNAMIC) Connection connection) {
- AbstractSpan> parent = tracer.getActive();
- if (parent == null) {
- return null;
- }
-
- Span span = parent.createExitSpan();
- if (span == null) {
- return null;
- }
-
- span.appendToName("LDAP ").appendToName(methodName)
- .withType("external")
- .withSubtype("ldap");
-
- if (connection != null) {
- span.getContext()
- .getDestination().withAddress(connection.host).withPort(connection.port)
- .getService().getResource().append(connection.host).append(":").append(connection.port);
- }
-
- return span.activate();
- }
-
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
- public static void onExit(@Advice.Enter @Nullable Object spanObj, @Nullable @Advice.Return LdapResult ldapResult, @Nullable @Advice.Thrown Throwable t) {
- Span span = (Span) spanObj;
- if (span != null) {
- span.withOutcome((ldapResult != null && ldapResult.status == 0 /* LDAP_SUCCESS */) ? Outcome.SUCCESS : Outcome.FAILURE)
- .captureException(t)
- .deactivate().end();
- }
- }
-}
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java b/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java
deleted file mode 100644
index edf5bec64d..0000000000
--- a/apm-agent-plugins/apm-java-ldap-plugin/src/main/java/co/elastic/apm/agent/java_ldap/LdapClientInstrumentation.java
+++ /dev/null
@@ -1,59 +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.java_ldap;
-
-import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
-import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.matcher.ElementMatcher;
-
-import java.util.Collection;
-import java.util.Collections;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-
-public class LdapClientInstrumentation extends TracerAwareInstrumentation {
-
- @Override
- public ElementMatcher super TypeDescription> getTypeMatcher() {
- return named("com.sun.jndi.ldap.LdapClient");
- }
-
- @Override
- public ElementMatcher super MethodDescription> getMethodMatcher() {
- return named("authenticate")
- .or(named("add"))
- .or(named("compare"))
- .or(named("delete"))
- .or(named("extendedOp"))
- .or(named("moddn"))
- .or(named("modify"))
- .or(named("search"));
- }
-
- @Override
- public String getAdviceClassName() {
- return "co.elastic.apm.agent.java_ldap.LdapClientAdvice";
- }
-
- @Override
- public Collection getInstrumentationGroupNames() {
- return Collections.singletonList("java-ldap");
- }
-}
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
deleted file mode 100644
index 4f5d5f9bf4..0000000000
--- a/apm-agent-plugins/apm-java-ldap-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
+++ /dev/null
@@ -1 +0,0 @@
-co.elastic.apm.agent.java_ldap.LdapClientInstrumentation
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java b/apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java
deleted file mode 100644
index b6872f740d..0000000000
--- a/apm-agent-plugins/apm-java-ldap-plugin/src/test/java/co/elastic/apm/agent/java_ldap/LdapClientAdviceTest.java
+++ /dev/null
@@ -1,139 +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.java_ldap;
-
-import co.elastic.apm.agent.AbstractInstrumentationTest;
-import co.elastic.apm.agent.impl.transaction.Outcome;
-import co.elastic.apm.agent.impl.transaction.Span;
-import co.elastic.apm.agent.impl.transaction.Transaction;
-import co.elastic.apm.agent.testutils.TestPort;
-import com.unboundid.ldap.listener.InMemoryDirectoryServer;
-import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
-import com.unboundid.ldap.listener.InMemoryListenerConfig;
-import com.unboundid.ldif.LDIFReader;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import javax.naming.Context;
-import javax.naming.directory.InitialDirContext;
-import java.util.Hashtable;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class LdapClientAdviceTest extends AbstractInstrumentationTest {
-
- private static InMemoryDirectoryServer ldapServer;
-
- @BeforeAll
- static void startServer() throws Exception {
- InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
- config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("test", TestPort.getAvailableRandomPort()));
-
- ldapServer = new InMemoryDirectoryServer(config);
- ldapServer.importFromLDIF(true, new LDIFReader(LdapClientAdviceTest.class.getResourceAsStream("/test.ldif")));
- ldapServer.startListening();
- }
-
- @AfterAll
- static void stopServer() {
- ldapServer.shutDown(true);
- }
-
- @Test
- void testSuccessfulAuthentication() throws Exception {
- Hashtable environment = getEnvironment();
-
- Transaction transaction = startTestRootTransaction();
- try {
- new InitialDirContext(environment).close();
- } catch (Exception ignored) {
- } finally {
- transaction.deactivate().end();
- }
-
- List spans = reporter.getSpans();
- assertThat(spans.size()).isEqualTo(1);
-
- assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
- }
-
- @Test
- void testUnsuccessfulAuthentication() {
- Hashtable environment = getEnvironment();
- environment.put(Context.SECURITY_CREDENTIALS, "wrong password");
-
- Transaction transaction = startTestRootTransaction();
- try {
- new InitialDirContext(environment).close();
- } catch (Exception ignored) {
- ignored.printStackTrace();
- } finally {
- transaction.deactivate().end();
- }
-
- List spans = reporter.getSpans();
- assertThat(spans.size()).isEqualTo(1);
-
- assertSpan(spans.get(0), "authenticate", Outcome.FAILURE);
- }
-
- @Test
- void testSearch() {
- Hashtable environment = getEnvironment();
-
- Transaction transaction = startTestRootTransaction();
- try {
- InitialDirContext context = new InitialDirContext(environment);
- context.search("dc=example,dc=com", "(&(objectClass=person)(uid=tobiasstadler))", null);
- context.close();
- } catch (Exception ignored) {
- } finally {
- transaction.deactivate().end();
- }
-
- List spans = reporter.getSpans();
- assertThat(spans.size()).isEqualTo(2);
-
- assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
- assertSpan(spans.get(1), "search", Outcome.SUCCESS);
- }
-
- private static Hashtable getEnvironment() {
- Hashtable environment = new Hashtable<>();
-
- environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
- environment.put(Context.PROVIDER_URL, "ldap://localhost:" + ldapServer.getListenPort());
- environment.put(Context.SECURITY_AUTHENTICATION, "simple");
- environment.put(Context.SECURITY_PRINCIPAL, "cn=Tobias Stadler,ou=Users,dc=example,dc=com");
- environment.put(Context.SECURITY_CREDENTIALS, "123456");
-
- return environment;
- }
-
- static void assertSpan(Span span, String method, Outcome outcome) {
- assertThat(span.getNameAsString()).isEqualTo("LDAP " + method);
- assertThat(span.getType()).isEqualTo("external");
- assertThat(span.getSubtype()).isEqualTo("ldap");
- assertThat(span.getOutcome()).isEqualTo(outcome);
- assertThat(span.getContext().getDestination().getAddress().toString()).isEqualTo("localhost");
- assertThat(span.getContext().getDestination().getPort()).isEqualTo(ldapServer.getListenPort());
- }
-}
diff --git a/apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif b/apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif
deleted file mode 100644
index 01bf3ed14c..0000000000
--- a/apm-agent-plugins/apm-java-ldap-plugin/src/test/resources/test.ldif
+++ /dev/null
@@ -1,24 +0,0 @@
-dn: dc=example,dc=com
-objectClass: domain
-objectClass: top
-dc: example
-
-dn: ou=Users,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: Users
-
-dn: ou=Groups,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: Groups
-
-dn: cn=Tobias Stadler,ou=Users,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: organizationalPerson
-objectClass: person
-objectClass: top
-cn: Tobias Stadler
-sn: Stadler
-uid: tobiasstadler
-userPassword: 123456
diff --git a/apm-agent-plugins/pom.xml b/apm-agent-plugins/pom.xml
index ccffba1871..2319871c24 100644
--- a/apm-agent-plugins/pom.xml
+++ b/apm-agent-plugins/pom.xml
@@ -69,7 +69,6 @@
apm-jaxws-plugin-jakartaee-testapm-jaxrs-plugin-jakartaee-testapm-scheduled-annotation-plugin-jakartaee-test
- apm-java-ldap-plugin
diff --git a/apm-agent/pom.xml b/apm-agent/pom.xml
index 332acb9b7a..6c35eb77f6 100644
--- a/apm-agent/pom.xml
+++ b/apm-agent/pom.xml
@@ -316,11 +316,6 @@
apm-awslambda-plugin${project.version}
-
- ${project.groupId}
- apm-java-ldap-plugin
- ${project.version}
-
diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc
index 9f5c8b2506..0ad9bfefbf 100644
--- a/docs/configuration.asciidoc
+++ b/docs/configuration.asciidoc
@@ -718,7 +718,7 @@ you should add an additional entry to this list (make sure to also include the d
==== `enable_instrumentations` (added[1.28.0])
A list of instrumentations which should be selectively enabled.
-Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>.
When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>.
@@ -746,7 +746,7 @@ NOTE: Changing this value at runtime can slow down the application temporarily.
==== `disable_instrumentations` (added[1.0.0,Changing this value at runtime is possible since version 1.15.0])
A list of instrumentations which should be disabled.
-Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
For version `1.25.0` and later, use <> to enable experimental instrumentations.
NOTE: Changing this value at runtime can slow down the application temporarily.
@@ -2999,7 +2999,7 @@ The default unit for this option is `ms`.
# sanitize_field_names=password,passwd,pwd,secret,*key,*token*,*session*,*credit*,*card*,*auth*,set-cookie
# A list of instrumentations which should be selectively enabled.
-# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
# When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>.
# When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>.
#
@@ -3012,7 +3012,7 @@ The default unit for this option is `ms`.
# enable_instrumentations=
# A list of instrumentations which should be disabled.
-# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `java-ldap`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
# For version `1.25.0` and later, use <> to enable experimental instrumentations.
#
# NOTE: Changing this value at runtime can slow down the application temporarily.
diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc
index 0c5d7bb934..bee5f30a2c 100644
--- a/docs/supported-technologies.asciidoc
+++ b/docs/supported-technologies.asciidoc
@@ -336,10 +336,6 @@ The spans are named after the schema ``, for example `GET elastic
|
| 1.25.0
-|LdapClient
-|
-|
-| 1.29.0
|===
From b5b3ca1b26abe2e85abcfb3304350374d03a8fd1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 28 Jan 2022 09:59:20 -0500
Subject: [PATCH 16/46] Bump version.slf4j from 2.0.0-alpha0 to 2.0.0-alpha6
(#2396)
* Bump version.slf4j from 2.0.0-alpha0 to 2.0.0-alpha6
Bumps `version.slf4j` from 2.0.0-alpha0 to 2.0.0-alpha6.
Updates `slf4j-api` from 2.0.0-alpha0 to 2.0.0-alpha6
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/commits/v_2.0.0-alpha6)
Updates `jul-to-slf4j` from 1.7.32 to 2.0.0-alpha6
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.32...v_2.0.0-alpha6)
---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
dependency-type: direct:production
update-type: version-update:semver-patch
- dependency-name: org.slf4j:jul-to-slf4j
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
* keep slf4j 1.7.x
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sylvain Juge
From 53204c6986e2e00ae0bc354d0aefc4259a61eb64 Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Fri, 28 Jan 2022 17:42:28 +0100
Subject: [PATCH 17/46] Support for Java/Jakarta WebSocket ServerEndpoint
(#2281)
---
CHANGELOG.asciidoc | 1 +
.../apm-jakarta-websocket-plugin/pom.xml | 34 +++++
.../BaseServerEndpointInstrumentation.java | 119 ++++++++++++++++++
.../JakartaServerEndpointInstrumentation.java | 61 +++++++++
.../JavaxServerEndpointInstrumentation.java | 61 +++++++++
...ic.apm.agent.sdk.ElasticApmInstrumentation | 2 +
...BaseServerEndpointInstrumentationTest.java | 103 +++++++++++++++
...artaServerEndpointInstrumentationTest.java | 43 +++++++
...avaxServerEndpointInstrumentationTest.java | 43 +++++++
.../endpoint/JakartaServerEndpoint.java | 49 ++++++++
.../endpoint/JavaxServerEndpoint.java | 49 ++++++++
.../websocket/endpoint/WebSocketEndpoint.java | 30 +++++
apm-agent-plugins/pom.xml | 1 +
apm-agent/pom.xml | 5 +
docs/configuration.asciidoc | 8 +-
docs/supported-technologies.asciidoc | 5 +
16 files changed, 610 insertions(+), 4 deletions(-)
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java
create mode 100644 apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.java
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 12ad7b3f91..fcf335785b 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -34,6 +34,7 @@ endif::[]
* 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]
* Added support for instrumenting Struts 2 static resource requests - {pull}1949[#1949]
+* Added support for Java/Jakarta WebSocket ServerEndpoint - {pull}2281[#2281]
[float]
===== Bug fixes
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml b/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml
new file mode 100644
index 0000000000..55f1cd7ef4
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+
+
+ apm-agent-plugins
+ co.elastic.apm
+ 1.28.5-SNAPSHOT
+
+
+ apm-jakarta-websocket-plugin
+ ${project.groupId}:${project.artifactId}
+
+
+ ${project.basedir}/../..
+
+
+
+
+ javax.websocket
+ javax.websocket-api
+ 1.1
+ provided
+
+
+ jakarta.websocket
+ jakarta.websocket-api
+ 2.0.0
+ provided
+
+
+
+
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java
new file mode 100644
index 0000000000..279b630586
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentation.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket;
+
+import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
+import co.elastic.apm.agent.impl.ElasticApmTracer;
+import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration;
+import co.elastic.apm.agent.impl.transaction.Outcome;
+import co.elastic.apm.agent.impl.transaction.Transaction;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+
+import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass;
+import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.isInAnyPackage;
+import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_HIGH_LEVEL_FRAMEWORK;
+import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
+import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+
+public abstract class BaseServerEndpointInstrumentation extends TracerAwareInstrumentation {
+
+ private final Collection applicationPackages;
+
+ public BaseServerEndpointInstrumentation(ElasticApmTracer tracer) {
+ applicationPackages = tracer.getConfig(StacktraceConfiguration.class).getApplicationPackages();
+ }
+
+ @Override
+ public ElementMatcher.Junction getClassLoaderMatcher() {
+ return not(isBootstrapClassLoader())
+ .and(classLoaderCanLoadClass(getServerEndpointClassName()));
+ }
+
+ @Override
+ public ElementMatcher super NamedElement> getTypeMatcherPreFilter() {
+ return isInAnyPackage(applicationPackages, ElementMatchers.any());
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return isAnnotatedWith(named(getServerEndpointClassName()));
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return isAnnotatedWith(
+ namedOneOf("javax.websocket.OnOpen", "jakarta.websocket.OnOpen")
+ .or(namedOneOf("javax.websocket.OnMessage", "jakarta.websocket.OnMessage"))
+ .or(namedOneOf("javax.websocket.OnError", "jakarta.websocket.OnError"))
+ .or(namedOneOf("javax.websocket.OnClose", "jakarta.websocket.OnClose")));
+ }
+
+ protected abstract String getServerEndpointClassName();
+
+ protected static class BaseAdvice {
+
+ @Nullable
+ protected static Object startTransactionOrSetTransactionName(String signature, String frameworkName, @Nullable String frameworkVersion) {
+ Transaction currentTransaction = tracer.currentTransaction();
+ if (currentTransaction == null) {
+ Transaction rootTransaction = tracer.startRootTransaction(Thread.currentThread().getContextClassLoader());
+ if (rootTransaction != null) {
+ setTransactionName(rootTransaction, signature, frameworkName, frameworkVersion);
+ return rootTransaction.activate();
+ }
+ } else {
+ setTransactionName(currentTransaction, signature, frameworkName, frameworkVersion);
+ }
+
+ return null;
+ }
+
+ protected static void endTransaction(@Nullable Object transactionOrNull, @Advice.Thrown @Nullable Throwable t) {
+ if (transactionOrNull == null) {
+ return;
+ }
+
+ Transaction transaction = (Transaction) transactionOrNull;
+ try {
+ if (t != null) {
+ transaction.captureException(t).withOutcome(Outcome.FAILURE);
+ }
+ } finally {
+ transaction.deactivate().end();
+ }
+ }
+
+ private static void setTransactionName(Transaction transaction, String signature, String frameworkName, @Nullable String frameworkVersion) {
+ transaction.withName(signature, PRIO_HIGH_LEVEL_FRAMEWORK, false);
+ transaction.setFrameworkName(frameworkName);
+ transaction.setFrameworkVersion(frameworkVersion);
+ }
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.java
new file mode 100644
index 0000000000..6219ca5809
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentation.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket;
+
+import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
+import co.elastic.apm.agent.impl.ElasticApmTracer;
+import co.elastic.apm.agent.util.VersionUtils;
+import jakarta.websocket.server.ServerEndpoint;
+import net.bytebuddy.asm.Advice;
+
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class JakartaServerEndpointInstrumentation extends BaseServerEndpointInstrumentation {
+
+ public JakartaServerEndpointInstrumentation(ElasticApmTracer tracer) {
+ super(tracer);
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Arrays.asList("websocket", "jakarta-websocket");
+ }
+
+ @Override
+ protected String getServerEndpointClassName() {
+ return "jakarta.websocket.server.ServerEndpoint";
+ }
+
+ public static class AdviceClass extends BaseAdvice {
+
+ @Nullable
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object onMethodEnter(@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature) {
+ String frameworkVersion = VersionUtils.getVersion(ServerEndpoint.class, "jakarta.websocket", "jakarta.websocket-api");
+ return startTransactionOrSetTransactionName(signature, "Jakarta WebSocket", frameworkVersion);
+ }
+
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onMethodExit(@Advice.Enter @Nullable Object transactionOrNull, @Advice.Thrown @Nullable Throwable t) {
+ endTransaction(transactionOrNull, t);
+ }
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.java
new file mode 100644
index 0000000000..4886047df3
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentation.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket;
+
+import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
+import co.elastic.apm.agent.impl.ElasticApmTracer;
+import co.elastic.apm.agent.util.VersionUtils;
+import net.bytebuddy.asm.Advice;
+
+import javax.annotation.Nullable;
+import javax.websocket.server.ServerEndpoint;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class JavaxServerEndpointInstrumentation extends BaseServerEndpointInstrumentation {
+
+ public JavaxServerEndpointInstrumentation(ElasticApmTracer tracer) {
+ super(tracer);
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Arrays.asList("websocket", "javax-websocket");
+ }
+
+ @Override
+ protected String getServerEndpointClassName() {
+ return "javax.websocket.server.ServerEndpoint";
+ }
+
+ public static class AdviceClass extends BaseServerEndpointInstrumentation.BaseAdvice {
+
+ @Nullable
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object onMethodEnter(@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature) {
+ String frameworkVersion = VersionUtils.getVersion(ServerEndpoint.class, "javax.websocket", "javax.websocket-api");
+ return startTransactionOrSetTransactionName(signature, "Java WebSocket", frameworkVersion);
+ }
+
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onMethodExit(@Advice.Enter @Nullable Object transactionOrNull, @Advice.Thrown @Nullable Throwable t) {
+ endTransaction(transactionOrNull, t);
+ }
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
new file mode 100644
index 0000000000..8ae73700f6
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
@@ -0,0 +1,2 @@
+co.elastic.apm.agent.websocket.JakartaServerEndpointInstrumentation
+co.elastic.apm.agent.websocket.JavaxServerEndpointInstrumentation
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java
new file mode 100644
index 0000000000..375ce6ae92
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/BaseServerEndpointInstrumentationTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket;
+
+import co.elastic.apm.agent.AbstractInstrumentationTest;
+import co.elastic.apm.agent.impl.transaction.Transaction;
+import co.elastic.apm.agent.websocket.endpoint.WebSocketEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+abstract class BaseServerEndpointInstrumentationTest extends AbstractInstrumentationTest {
+
+ private final WebSocketEndpoint serverEndpoint;
+
+ protected BaseServerEndpointInstrumentationTest(WebSocketEndpoint serverEndpoint) {
+ this.serverEndpoint = serverEndpoint;
+ }
+
+ @Test
+ void testOnOpenWithActiveTransaction() {
+ Transaction transaction = startTestRootTransaction();
+ try {
+ serverEndpoint.onOpen();
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ assertReportedTransactionNameAndFramework("onOpen");
+ }
+
+ @Test
+ void testOnOpenWithoutActiveTransaction() {
+ serverEndpoint.onOpen();
+
+ assertReportedTransactionNameAndFramework("onOpen");
+ }
+
+ @Test
+ void testOnMessage() {
+ Transaction transaction = startTestRootTransaction();
+ try {
+ serverEndpoint.onMessage("");
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ assertReportedTransactionNameAndFramework("onMessage");
+ }
+
+ @Test
+ void testOnError() {
+ Transaction transaction = startTestRootTransaction();
+ try {
+ serverEndpoint.onError();
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ assertReportedTransactionNameAndFramework("onError");
+ }
+
+ @Test
+ void testOnClose() {
+ Transaction transaction = startTestRootTransaction();
+ try {
+ serverEndpoint.onClose();
+ } finally {
+ transaction.deactivate().end();
+ }
+
+ assertReportedTransactionNameAndFramework("onClose");
+ }
+
+ protected abstract String getWebSocketServerEndpointClassName();
+
+ protected abstract String getFrameworkName();
+
+ protected abstract String getFrameworkVersion();
+
+ private void assertReportedTransactionNameAndFramework(String methodName) {
+ Transaction transaction = reporter.getFirstTransaction();
+ assertThat(transaction.getNameAsString()).isEqualTo(getWebSocketServerEndpointClassName() + '#' + methodName);
+ assertThat(transaction.getFrameworkName()).isEqualTo(getFrameworkName());
+ assertThat(transaction.getFrameworkVersion()).isEqualTo(getFrameworkVersion());
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java
new file mode 100644
index 0000000000..2c973a81a8
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JakartaServerEndpointInstrumentationTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket;
+
+import co.elastic.apm.agent.websocket.endpoint.JakartaServerEndpoint;
+
+class JakartaServerEndpointInstrumentationTest extends BaseServerEndpointInstrumentationTest {
+
+ JakartaServerEndpointInstrumentationTest() {
+ super(new JakartaServerEndpoint());
+ }
+
+ @Override
+ protected String getWebSocketServerEndpointClassName() {
+ return JakartaServerEndpoint.class.getSimpleName();
+ }
+
+ @Override
+ protected String getFrameworkName() {
+ return "Jakarta WebSocket";
+ }
+
+ @Override
+ protected String getFrameworkVersion() {
+ return "2.0.0";
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java
new file mode 100644
index 0000000000..bd7dde50a3
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/JavaxServerEndpointInstrumentationTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket;
+
+import co.elastic.apm.agent.websocket.endpoint.JavaxServerEndpoint;
+
+class JavaxServerEndpointInstrumentationTest extends BaseServerEndpointInstrumentationTest {
+
+ JavaxServerEndpointInstrumentationTest() {
+ super(new JavaxServerEndpoint());
+ }
+
+ @Override
+ protected String getWebSocketServerEndpointClassName() {
+ return JavaxServerEndpoint.class.getSimpleName();
+ }
+
+ @Override
+ protected String getFrameworkName() {
+ return "Java WebSocket";
+ }
+
+ @Override
+ protected String getFrameworkVersion() {
+ return "1.1";
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java
new file mode 100644
index 0000000000..5e4f830deb
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JakartaServerEndpoint.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket.endpoint;
+
+import jakarta.websocket.OnClose;
+import jakarta.websocket.OnError;
+import jakarta.websocket.OnMessage;
+import jakarta.websocket.OnOpen;
+import jakarta.websocket.server.ServerEndpoint;
+
+@ServerEndpoint("/path")
+public class JakartaServerEndpoint implements WebSocketEndpoint {
+
+ @OnOpen
+ @Override
+ public void onOpen() {
+ }
+
+ @OnMessage
+ @Override
+ public void onMessage(String message) {
+ }
+
+ @OnError
+ @Override
+ public void onError() {
+ }
+
+ @OnClose
+ @Override
+ public void onClose() {
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java
new file mode 100644
index 0000000000..0b41efc6a2
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/JavaxServerEndpoint.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket.endpoint;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+@ServerEndpoint("/path")
+public class JavaxServerEndpoint implements WebSocketEndpoint {
+
+ @OnOpen
+ @Override
+ public void onOpen() {
+ }
+
+ @OnMessage
+ @Override
+ public void onMessage(String message) {
+ }
+
+ @OnError
+ @Override
+ public void onError() {
+ }
+
+ @OnClose
+ @Override
+ public void onClose() {
+ }
+}
diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.java b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.java
new file mode 100644
index 0000000000..f0b2c174e5
--- /dev/null
+++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/src/test/java/co/elastic/apm/agent/websocket/endpoint/WebSocketEndpoint.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.websocket.endpoint;
+
+public interface WebSocketEndpoint {
+
+ void onOpen();
+
+ void onMessage(String message);
+
+ void onError();
+
+ void onClose();
+}
diff --git a/apm-agent-plugins/pom.xml b/apm-agent-plugins/pom.xml
index 2319871c24..2f58ca9798 100644
--- a/apm-agent-plugins/pom.xml
+++ b/apm-agent-plugins/pom.xml
@@ -69,6 +69,7 @@
apm-jaxws-plugin-jakartaee-testapm-jaxrs-plugin-jakartaee-testapm-scheduled-annotation-plugin-jakartaee-test
+ apm-jakarta-websocket-plugin
diff --git a/apm-agent/pom.xml b/apm-agent/pom.xml
index 6c35eb77f6..7e1f21bb5a 100644
--- a/apm-agent/pom.xml
+++ b/apm-agent/pom.xml
@@ -316,6 +316,11 @@
apm-awslambda-plugin${project.version}
+
+ ${project.groupId}
+ apm-jakarta-websocket-plugin
+ ${project.version}
+
diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc
index 0ad9bfefbf..daa869d7cc 100644
--- a/docs/configuration.asciidoc
+++ b/docs/configuration.asciidoc
@@ -718,7 +718,7 @@ you should add an additional entry to this list (make sure to also include the d
==== `enable_instrumentations` (added[1.28.0])
A list of instrumentations which should be selectively enabled.
-Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`.
When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>.
When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>.
@@ -746,7 +746,7 @@ NOTE: Changing this value at runtime can slow down the application temporarily.
==== `disable_instrumentations` (added[1.0.0,Changing this value at runtime is possible since version 1.15.0])
A list of instrumentations which should be disabled.
-Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`.
For version `1.25.0` and later, use <> to enable experimental instrumentations.
NOTE: Changing this value at runtime can slow down the application temporarily.
@@ -2999,7 +2999,7 @@ The default unit for this option is `ms`.
# sanitize_field_names=password,passwd,pwd,secret,*key,*token*,*session*,*credit*,*card*,*auth*,set-cookie
# A list of instrumentations which should be selectively enabled.
-# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`.
# When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>.
# When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>.
#
@@ -3012,7 +3012,7 @@ The default unit for this option is `ms`.
# enable_instrumentations=
# A list of instrumentations which should be disabled.
-# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `javalin`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`.
+# Valid options are `annotations`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `kafka`, `lettuce`, `log4j1-ecs`, `log4j2-ecs`, `log4j2-error`, `logback-ecs`, `logging`, `micrometer`, `mongodb-client`, `okhttp`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`.
# For version `1.25.0` and later, use <> to enable experimental instrumentations.
#
# NOTE: Changing this value at runtime can slow down the application temporarily.
diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc
index bee5f30a2c..b5ca5d1d89 100644
--- a/docs/supported-technologies.asciidoc
+++ b/docs/supported-technologies.asciidoc
@@ -171,6 +171,11 @@ rendering
|
|1.25.0
+|Java API for WebSocket
+|1.0
+|Captures methods annotated with `@OnOpen`, `@OnMessage`, `@OnError`, or `@OnClose` as transactions for classes that are annotated with `@ServerEndpoint`.
+|1.29.0
+
|===
From 8b16c340c1889d2a5e6785f4f05b6418688c34ec Mon Sep 17 00:00:00 2001
From: Tobias Stadler
Date: Fri, 28 Jan 2022 17:44:13 +0100
Subject: [PATCH 18/46] Support for setting the service name on Log4j2's
EcsLayout (#2296)
---
CHANGELOG.asciidoc | 1 +
.../apm/agent/impl/ElasticApmTracer.java | 2 +-
.../apm-ecs-logging-plugin/pom.xml | 40 +++++++++++
.../Log4j2ServiceNameInstrumentation.java | 67 +++++++++++++++++++
...ic.apm.agent.sdk.ElasticApmInstrumentation | 1 +
.../Log4j2ServiceNameInstrumentationTest.java | 62 +++++++++++++++++
apm-agent-plugins/pom.xml | 1 +
apm-agent/pom.xml | 5 ++
docs/supported-technologies.asciidoc | 4 ++
9 files changed, 182 insertions(+), 1 deletion(-)
create mode 100644 apm-agent-plugins/apm-ecs-logging-plugin/pom.xml
create mode 100644 apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java
create mode 100644 apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
create mode 100644 apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index fcf335785b..27e5901dad 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -35,6 +35,7 @@ endif::[]
* Added support for overwritting the service version per classloader - {pull}1726[#1726]
* 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]
[float]
===== Bug fixes
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 ccd759dc5a..50faae45c7 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
@@ -771,7 +771,7 @@ public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader,
}
@Nullable
- private ServiceInfo getServiceInfo(@Nullable ClassLoader initiatingClassLoader) {
+ public ServiceInfo getServiceInfo(@Nullable ClassLoader initiatingClassLoader) {
if (initiatingClassLoader == null) {
return null;
}
diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml
new file mode 100644
index 0000000000..5e74f0f8c1
--- /dev/null
+++ b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+
+ apm-agent-plugins
+ co.elastic.apm
+ 1.28.5-SNAPSHOT
+
+
+ apm-ecs-logging-plugin
+ ${project.groupId}:${project.artifactId}
+
+
+ ${project.basedir}/../..
+
+
+
+
+ co.elastic.logging
+ log4j-ecs-layout
+ 1.2.0
+ provided
+
+
+ co.elastic.logging
+ log4j2-ecs-layout
+ 1.2.0
+ provided
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.14.1
+ provided
+
+
+
+
diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java
new file mode 100644
index 0000000000..eef1f2e3b4
--- /dev/null
+++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentation.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.ecs_logging;
+
+import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
+import co.elastic.apm.agent.configuration.CoreConfiguration;
+import co.elastic.apm.agent.configuration.ServiceInfo;
+import co.elastic.apm.agent.impl.ElasticApmTracer;
+import co.elastic.apm.agent.impl.GlobalTracer;
+import co.elastic.logging.log4j2.EcsLayout;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class Log4j2ServiceNameInstrumentation extends TracerAwareInstrumentation {
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named("co.elastic.logging.log4j2.EcsLayout$Builder");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return named("build");
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Arrays.asList("logging", "log4j2-ecs");
+ }
+
+ public static class AdviceClass {
+
+ private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl();
+
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static void onEnter(@Advice.This EcsLayout.Builder builder) {
+ if (builder.getServiceName() == null || builder.getServiceName().isEmpty()) {
+ ServiceInfo serviceInfo = tracer.getServiceInfo(Thread.currentThread().getContextClassLoader());
+ String configuredServiceName = tracer.getConfig(CoreConfiguration.class).getServiceName();
+ builder.setServiceName(serviceInfo != null ? serviceInfo.getServiceName() : configuredServiceName);
+ }
+ }
+ }
+}
diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
new file mode 100644
index 0000000000..9ee68e9e14
--- /dev/null
+++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
@@ -0,0 +1 @@
+co.elastic.apm.agent.ecs_logging.Log4j2ServiceNameInstrumentation
diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java
new file mode 100644
index 0000000000..72b885bfb4
--- /dev/null
+++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2ServiceNameInstrumentationTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package co.elastic.apm.agent.ecs_logging;
+
+import co.elastic.apm.agent.AbstractInstrumentationTest;
+import co.elastic.apm.agent.configuration.CoreConfiguration;
+import co.elastic.logging.log4j2.EcsLayout;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+class Log4j2ServiceNameInstrumentationTest extends AbstractInstrumentationTest {
+
+ @BeforeAll
+ static void setUp() {
+ when(tracer.getConfig(CoreConfiguration.class).getServiceName()).thenReturn("foo");
+ }
+
+ @Test
+ void testBuildWithNoServiceNameSet() throws JsonProcessingException {
+ EcsLayout ecsLayout = EcsLayout.newBuilder().build();
+ assertThat(getServiceName(ecsLayout.toSerializable(createLogEvent()))).isEqualTo("foo");
+ }
+
+ @Test
+ void testBuildWithServiceNameSet() throws JsonProcessingException {
+ EcsLayout ecsLayout = EcsLayout.newBuilder().setServiceName("bar").build();
+ assertThat(getServiceName(ecsLayout.toSerializable(createLogEvent()))).isEqualTo("bar");
+ }
+
+ private static Log4jLogEvent createLogEvent() {
+ return new Log4jLogEvent("", null, "", null, new SimpleMessage(), null, null);
+ }
+
+ private static String getServiceName(String json) throws JsonProcessingException {
+ return (String) new ObjectMapper().readValue(json, Map.class).get("service.name");
+ }
+}
diff --git a/apm-agent-plugins/pom.xml b/apm-agent-plugins/pom.xml
index 2f58ca9798..3372ee492c 100644
--- a/apm-agent-plugins/pom.xml
+++ b/apm-agent-plugins/pom.xml
@@ -70,6 +70,7 @@
apm-jaxrs-plugin-jakartaee-testapm-scheduled-annotation-plugin-jakartaee-testapm-jakarta-websocket-plugin
+ apm-ecs-logging-plugin
diff --git a/apm-agent/pom.xml b/apm-agent/pom.xml
index 7e1f21bb5a..31292ddea3 100644
--- a/apm-agent/pom.xml
+++ b/apm-agent/pom.xml
@@ -321,6 +321,11 @@
apm-jakarta-websocket-plugin${project.version}
+
+ ${project.groupId}
+ apm-ecs-logging-plugin
+ ${project.version}
+
diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc
index b5ca5d1d89..b2272ad8eb 100644
--- a/docs/supported-technologies.asciidoc
+++ b/docs/supported-technologies.asciidoc
@@ -500,6 +500,8 @@ ECS Reformatting - 2.6+
|When <> is set to `true`,
the agent will add a https://logging.apache.org/log4j/2.x/manual/thread-context.html[ThreadContext] entry for `trace.id` and `transaction.id`.
+The agent sets the service name for the `EcsLayout` if not provided explicitly (since 1.29.0).
+
When <> is enabled, logs will be automatically reformatted into
ECS-compatible format (since 1.22.0, experimental)
@@ -509,6 +511,8 @@ When doing so, the ID corresponding the captured error (`error.id`) is added to
Error capturing - 1.10.0
+ECS Service Name - 1.29.0
+
ECS Reformatting - 1.22.0
From 73fb6958d08b4b449d3cd0373d6c934a70d6cb7e Mon Sep 17 00:00:00 2001
From: Victor Martinez
Date: Mon, 31 Jan 2022 11:21:18 +0000
Subject: [PATCH 19/46] [CI] new stages for running the compile and test maven
goals on Windows (#1703)
* [CI] New stage: install maven goal on Windows
* Use windows environment
* Use params for the java version
* HOME was empty, let's force the existing location
* Split install in two sequential stages
* Add resilience with the maven central repo
* skip git-commit-id-plugin on Windows
* cleanup
* single block for windows build & test
* disable codecov for now
* try to fix/debug missing 'cmd' in PATH
* fix: PATH env var
* fix: duplicate environment section
* splint build & test on windows
* try to update scala maven plugin
Co-authored-by: eyalkoren <41850454+eyalkoren@users.noreply.github.com>
Co-authored-by: Felix Barnsteiner
Co-authored-by: Sylvain Juge
Co-authored-by: Ivan Fernandez Calvo
---
Jenkinsfile | 124 ++++++++++++++----
.../apm-scala-concurrent-plugin/pom.xml | 2 +-
2 files changed, 96 insertions(+), 30 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 27d5c4166f..bdd25da9fa 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -16,6 +16,7 @@ pipeline {
ITS_PIPELINE = 'apm-integration-tests-selector-mbp/main'
MAVEN_CONFIG = '-Dmaven.repo.local=.m2'
OPBEANS_REPO = 'opbeans-java'
+ JAVA_VERSION = "${params.JAVA_VERSION}"
JOB_GCS_BUCKET_STASH = 'apm-ci-temp'
JOB_GCS_CREDENTIALS = 'apm-ci-gcs-plugin'
}
@@ -33,6 +34,7 @@ pipeline {
issueCommentTrigger("(${obltGitHubComments()}|^run (jdk compatibility|benchmark|integration|end-to-end) tests)")
}
parameters {
+ string(name: 'JAVA_VERSION', defaultValue: 'java11', description: 'Java version to build & test')
string(name: 'MAVEN_CONFIG', defaultValue: '-V -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dhttps.protocols=TLSv1.2 -Dmaven.wagon.http.retryHandler.count=3 -Dmaven.wagon.httpconnectionManager.ttlSeconds=25', description: 'Additional maven options.')
// Note about GH checks and optional steps
@@ -59,6 +61,10 @@ pipeline {
// disabled by default, not required for merge
// opt-in with 'ci:jdk-compatibility' tag on PR
booleanParam(name: 'jdk_compatibility_ci', defaultValue: false, description: 'Enable JDK compatibility tests')
+
+ // disabled by default, not required for merge
+ // opt-in with 'ci:windows' tag on PR
+ booleanParam(name: 'windows_ci', defaultValue: false, description: 'Enable Windows build & tests')
}
stages {
stage('Checkout') {
@@ -82,7 +88,7 @@ pipeline {
}
}
}
- stage('Builds'){
+ stage('Builds') {
options { skipDefaultCheckout() }
when {
// Tags are not required to be built/tested.
@@ -92,19 +98,21 @@ pipeline {
}
environment {
HOME = "${env.WORKSPACE}"
- JAVA_HOME = "${env.HUDSON_HOME}/.java/java11"
- PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ JAVA_HOME = "${env.HUDSON_HOME}/.java/${env.JAVA_VERSION}"
MAVEN_CONFIG = "${params.MAVEN_CONFIG} ${env.MAVEN_CONFIG}"
}
stages {
/**
- Build on a linux environment.
+ * Build on a linux environment.
*/
stage('Build') {
when {
beforeAgent true
expression { return env.ONLY_DOCS == "false" }
}
+ environment {
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ }
steps {
withGithubNotify(context: 'Build', tab: 'artifacts') {
deleteDir()
@@ -124,9 +132,12 @@ pipeline {
}
stashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
archiveArtifacts allowEmptyArchive: true,
- artifacts: "${BASE_DIR}/elastic-apm-agent/target/elastic-apm-agent-*.jar,${BASE_DIR}/apm-agent-attach/target/apm-agent-attach-*.jar,\
- ${BASE_DIR}/apm-agent-attach-cli/target/apm-agent-attach-cli-*.jar,${BASE_DIR}/apm-agent-api/target/apm-agent-api-*.jar,\
- ${BASE_DIR}/target/site/aggregate-third-party-report.html",
+ artifacts: "\
+ ${BASE_DIR}/elastic-apm-agent/target/elastic-apm-agent-*.jar,\
+ ${BASE_DIR}/apm-agent-attach/target/apm-agent-attach-*.jar,\
+ ${BASE_DIR}/apm-agent-attach-cli/target/apm-agent-attach-cli-*.jar,\
+ ${BASE_DIR}/apm-agent-api/target/apm-agent-api-*.jar,\
+ ${BASE_DIR}/target/site/aggregate-third-party-report.html",
onlyIfSuccessful: true
}
}
@@ -139,24 +150,24 @@ pipeline {
failFast true
parallel {
/**
- Run only unit test.
- */
+ * Run only unit tests
+ */
stage('Unit Tests') {
options { skipDefaultCheckout() }
when {
beforeAgent true
expression { return params.test_ci }
}
+ environment {
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ }
steps {
withGithubNotify(context: 'Unit Tests', tab: 'tests') {
deleteDir()
- unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
- dir("${BASE_DIR}"){
+ unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
+ dir("${BASE_DIR}") {
withOtelEnv() {
- sh """#!/bin/bash
- set -euxo pipefail
- ./mvnw test
- """
+ sh label: 'mvn test', script: './mvnw test'
}
}
}
@@ -167,6 +178,46 @@ pipeline {
}
}
}
+ /** *
+ * Build & Test on Windows environment
+ */
+ stage('Build & Test Windows') {
+ agent { label 'windows-2019-docker-immutable' }
+ options { skipDefaultCheckout() }
+ when {
+ beforeAgent true
+ allOf {
+ expression { return params.test_ci }
+ anyOf {
+ expression { return params.windows_ci }
+ expression { return env.GITHUB_COMMENT?.contains('windows tests') }
+ expression { matchesPrLabel(label: 'ci:windows') }
+ }
+ }
+ }
+ environment {
+ JAVA_HOME = "C:\\Users\\jenkins\\.java\\${env.JAVA_VERSION}"
+ PATH = "${env.JAVA_HOME}\\bin;${env.PATH}"
+ }
+ steps {
+ withGithubNotify(context: 'Build & Test Windows') {
+ deleteDir()
+ unstash 'source'
+ dir("${BASE_DIR}") {
+ echo "${env.PATH}"
+ retryWithSleep(retries: 5, seconds: 10) {
+ bat label: 'mvn clean install', script: "mvnw clean install -DskipTests=true -Dmaven.javadoc.skip=true -Dmaven.gitcommitid.skip=true"
+ }
+ bat label: 'mvn test', script: "mvnw test"
+ }
+ }
+ }
+ post {
+ always {
+ reportTestResults()
+ }
+ }
+ }
stage('Non-Application Server integration tests') {
agent { label 'linux && immutable' }
options { skipDefaultCheckout() }
@@ -180,11 +231,14 @@ pipeline {
not { changeRequest() }
}
}
+ environment {
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ }
steps {
withGithubNotify(context: 'Non-Application Server integration tests', tab: 'tests') {
deleteDir()
- unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
- dir("${BASE_DIR}"){
+ unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
+ dir("${BASE_DIR}") {
withOtelEnv() {
sh './mvnw -q -P ci-non-application-server-integration-tests verify'
}
@@ -210,11 +264,14 @@ pipeline {
not { changeRequest() }
}
}
+ environment {
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ }
steps {
withGithubNotify(context: 'Application Server integration tests', tab: 'tests') {
deleteDir()
- unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
- dir("${BASE_DIR}"){
+ unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
+ dir("${BASE_DIR}") {
withOtelEnv() {
sh './mvnw -q -P ci-application-server-integration-tests verify'
}
@@ -228,14 +285,15 @@ pipeline {
}
}
/**
- Run the benchmarks and store the results on ES.
- The result JSON files are also archive into Jenkins.
- */
+ * Run the benchmarks and store the results on ES.
+ * The result JSON files are also archive into Jenkins.
+ */
stage('Benchmarks') {
agent { label 'metal' }
options { skipDefaultCheckout() }
environment {
NO_BUILD = "true"
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
}
when {
beforeAgent true
@@ -266,11 +324,14 @@ pipeline {
}
}
/**
- Build javadoc files.
+ * Build javadoc
*/
stage('Javadoc') {
agent { label 'linux && immutable' }
options { skipDefaultCheckout() }
+ environment {
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ }
steps {
withGithubNotify(context: 'Javadoc') {
deleteDir()
@@ -325,25 +386,28 @@ pipeline {
}
}
}
+ environment {
+ PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
+ }
matrix {
agent { label 'linux && immutable' }
axes {
axis {
// the list of support java versions can be found in the infra repo (ansible/roles/java/defaults/main.yml)
- name 'JAVA_VERSION'
+ name 'JDK_VERSION'
// 'openjdk18' disabled for now see https://github.com/elastic/apm-agent-java/issues/2328
values 'openjdk17'
}
}
stages {
- stage('Test') {
+ stage('JDK Unit Tests') {
steps {
- withGithubNotify(context: "Unit Tests ${JAVA_VERSION}", tab: 'tests') {
+ withGithubNotify(context: "Unit Tests ${JDK_VERSION}", tab: 'tests') {
deleteDir()
unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
dir("${BASE_DIR}"){
withOtelEnv() {
- sh(label: "./mvnw test for ${JAVA_VERSION}", script: './mvnw test')
+ sh(label: "./mvnw test for ${JDK_VERSION}", script: './mvnw test')
}
}
}
@@ -359,7 +423,7 @@ pipeline {
}
}
}
- stage('Releases'){
+ stage('Releases') {
when {
anyOf {
branch 'main'
@@ -427,5 +491,7 @@ def reportTestResults(){
junit(allowEmptyResults: true,
keepLongStdio: true,
testResults: "${BASE_DIR}/**/junit-*.xml,${BASE_DIR}/**/TEST-*.xml")
- codecov(repo: env.REPO, basedir: "${BASE_DIR}", secret: "${CODECOV_SECRET}")
+
+ // disable codecov for now as it's not supported for windows
+ // codecov(repo: env.REPO, basedir: "${BASE_DIR}", secret: "${CODECOV_SECRET}")
}
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
index 4da5f105eb..6171f953d7 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
@@ -36,7 +36,7 @@
net.alchim31.mavenscala-maven-plugin
- 4.3.1
+ 4.5.62.13.2
From 4a4eab6a5f66b3315389c42810d66778fa03381d Mon Sep 17 00:00:00 2001
From: Victor Martinez
Date: Mon, 31 Jan 2022 14:18:05 +0000
Subject: [PATCH 20/46] [CI] Full build and test on Windows - weekly (#1705)
* [CI] single pipeline to run mvn verify on windows
* Remove GH commit staus since it is not a MBP
* Update .ci/jobs/apm-agent-java-windows.yml
* Enable weekly windows builds within the same default pipeline
* Refactor
* Use verify
* use the weekly one
* Use test instead of verify
The test goal doesn't require a Docker setup.
Verify executes the Docker-based integration tests.
Executing those on Windows doesn't add more coverage
and complicates the worker configuration.
* Update .ci/schedule-weekly.groovy
* fix whitespace
Co-authored-by: Felix Barnsteiner
Co-authored-by: Sylvain Juge
---
.ci/schedule-weekly.groovy | 19 ++++++++++---------
Jenkinsfile | 12 ++++++------
2 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/.ci/schedule-weekly.groovy b/.ci/schedule-weekly.groovy
index aa19116e3b..d15e4c0434 100644
--- a/.ci/schedule-weekly.groovy
+++ b/.ci/schedule-weekly.groovy
@@ -20,15 +20,16 @@ pipeline {
stages {
stage('Agent weekly exhaustive test') {
steps {
- build(job: 'apm-agent-java/apm-agent-java-mbp/main',
- parameters: [
- booleanParam(name: 'jdk_compatibility_ci', value: true),
- booleanParam(name: 'end_to_end_tests_ci', value: true),
- booleanParam(name: 'agent_integration_tests_ci', value: true),
- ],
- propagate: false,
- wait: false
- )
+ build(job: 'apm-agent-java/apm-agent-java-mbp/main',
+ parameters: [
+ booleanParam(name: 'jdk_compatibility_ci', value: true),
+ booleanParam(name: 'windows_ci', value: true),
+ booleanParam(name: 'end_to_end_tests_ci', value: true),
+ booleanParam(name: 'agent_integration_tests_ci', value: true),
+ ],
+ propagate: false,
+ wait: false
+ )
}
}
}
diff --git a/Jenkinsfile b/Jenkinsfile
index bdd25da9fa..33347722f1 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -31,7 +31,7 @@ pipeline {
quietPeriod(10)
}
triggers {
- issueCommentTrigger("(${obltGitHubComments()}|^run (jdk compatibility|benchmark|integration|end-to-end) tests)")
+ issueCommentTrigger("(${obltGitHubComments()}|^run (jdk compatibility|benchmark|integration|end-to-end|windows) tests)")
}
parameters {
string(name: 'JAVA_VERSION', defaultValue: 'java11', description: 'Java version to build & test')
@@ -103,8 +103,8 @@ pipeline {
}
stages {
/**
- * Build on a linux environment.
- */
+ * Build on a linux environment.
+ */
stage('Build') {
when {
beforeAgent true
@@ -306,7 +306,7 @@ pipeline {
steps {
withGithubNotify(context: 'Benchmarks', tab: 'artifacts') {
deleteDir()
- unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
+ unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
dir("${BASE_DIR}"){
withOtelEnv() {
sh './scripts/jenkins/run-benchmarks.sh'
@@ -325,7 +325,7 @@ pipeline {
}
/**
* Build javadoc
- */
+ */
stage('Javadoc') {
agent { label 'linux && immutable' }
options { skipDefaultCheckout() }
@@ -404,7 +404,7 @@ pipeline {
steps {
withGithubNotify(context: "Unit Tests ${JDK_VERSION}", tab: 'tests') {
deleteDir()
- unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
+ unstashV2(name: 'build', bucket: "${JOB_GCS_BUCKET_STASH}", credentialsId: "${JOB_GCS_CREDENTIALS}")
dir("${BASE_DIR}"){
withOtelEnv() {
sh(label: "./mvnw test for ${JDK_VERSION}", script: './mvnw test')
From c943c27b9e3864b06d5a1853b45b0341386d4db9 Mon Sep 17 00:00:00 2001
From: SylvainJuge
Date: Tue, 1 Feb 2022 09:07:44 +0100
Subject: [PATCH 21/46] Always use JDK default URL handler (#2429)
* Always use JDK default URL handler
* avoid test side-effects through instrumentation
* ensure cloud provider is safe to use
* update changelog
* fallback to original URL for safety
---
CHANGELOG.asciidoc | 1 +
.../impl/metadata/CloudMetadataProvider.java | 3 +-
.../apm/agent/report/ApmServerClient.java | 11 +-
.../elastic/apm/agent/report/HttpUtils.java | 44 ++++++
.../apm/agent/report/HttpUtilsTest.java | 137 +++++++++++++++++-
.../apm/agent/util/CustomEnvVariables.java | 3 -
6 files changed, 190 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 27e5901dad..ef8ecbaa63 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -49,6 +49,7 @@ paths. The BCI warmup is on by default and may be disabled through the internal
** APM headers conversion issue within dubbo transaction
* Fix External plugins automatic setting of span outcome - {pull}2376[#2376]
* Avoid early initialization of JMX on Weblogic - {pull}2420[#2420]
+* Ensure agent always uses JDK HTTP client - {pull}2429[#2429]
[[release-notes-1.x]]
=== Java Agent version 1.x
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/metadata/CloudMetadataProvider.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/metadata/CloudMetadataProvider.java
index 4d462cd935..21c9155c56 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/metadata/CloudMetadataProvider.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/metadata/CloudMetadataProvider.java
@@ -20,6 +20,7 @@
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.configuration.ServerlessConfiguration;
+import co.elastic.apm.agent.report.HttpUtils;
import co.elastic.apm.agent.util.ExecutorUtils;
import co.elastic.apm.agent.util.UrlConnectionUtils;
import com.dslplatform.json.DslJson;
@@ -415,7 +416,7 @@ private static Map deserialize(String input) throws IOException
}
private static String executeRequest(String url, String method, @Nullable Map headers, int queryTimeoutMs) throws IOException {
- HttpURLConnection urlConnection = (HttpURLConnection) UrlConnectionUtils.openUrlConnectionThreadSafely(new URL(url));
+ HttpURLConnection urlConnection = (HttpURLConnection) UrlConnectionUtils.openUrlConnectionThreadSafely(HttpUtils.withDefaultHandler(new URL(url)));
if (headers != null) {
for (String header : headers.keySet()) {
urlConnection.setRequestProperty(header, headers.get(header));
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerClient.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerClient.java
index 2679d269d1..268b72fad3 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerClient.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerClient.java
@@ -20,11 +20,11 @@
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.report.ssl.SslUtils;
+import co.elastic.apm.agent.sdk.logging.Logger;
+import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import co.elastic.apm.agent.util.UrlConnectionUtils;
import co.elastic.apm.agent.util.Version;
import co.elastic.apm.agent.util.VersionUtils;
-import co.elastic.apm.agent.sdk.logging.Logger;
-import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import org.stagemonitor.configuration.ConfigurationOption;
import javax.annotation.Nonnull;
@@ -112,7 +112,11 @@ public void onChange(ConfigurationOption> configurationOption, List oldVa
}
private void setServerUrls(List serverUrls) {
- this.serverUrls = serverUrls;
+ List newServersUrls = new ArrayList<>();
+ for (URL url : serverUrls) {
+ newServersUrls.add(HttpUtils.withDefaultHandler(url));
+ }
+ this.serverUrls = newServersUrls;
this.apmServerVersion = healthChecker.checkHealthAndGetMinVersion();
this.errorCount.set(0);
}
@@ -410,6 +414,7 @@ private static String getUserAgent(CoreConfiguration coreConfiguration) {
/**
* Escapes the provided string from characters that are disallowed within HTTP header comments.
* See spec- https://httpwg.org/specs/rfc7230.html#field.components
+ *
* @param headerFieldComment HTTP header comment value to be escaped
* @return the escaped header comment
*/
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/HttpUtils.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/HttpUtils.java
index b34aeb24c3..dfd581ff4d 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/HttpUtils.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/HttpUtils.java
@@ -18,6 +18,8 @@
*/
package co.elastic.apm.agent.report;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.stagemonitor.util.IOUtils;
import javax.annotation.Nullable;
@@ -25,10 +27,16 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLStreamHandler;
public class HttpUtils {
+ private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
+
private HttpUtils() {
}
@@ -65,4 +73,40 @@ public static void consumeAndClose(@Nullable HttpURLConnection connection) {
}
}
}
+
+ /**
+ * Rebuilds the provided URL with the default handler, as it may have been overriden by an application server at
+ * runtime and might prevent the agent from properly communicate through HTTP/HTTPS.
+ *
+ * @param url URL to rewrite
+ * @return equivalent URL with the default JDK handler
+ * @throws IllegalArgumentException if protocol is not supported or unable to access default handler
+ */
+ public static URL withDefaultHandler(URL url) {
+ // the default handler for URLs might be overridden by another implementation than the one shipped with the JDK
+ // for example, this happens on Weblogic application server and triggers classloading issues as the agent
+ // is unable to properly use the Weblogic classes as they aren't visible to the agent classloaders.
+
+ String protocol = url.getProtocol();
+ try {
+ return new URL(protocol, url.getHost(), url.getPort(), url.getFile(), handlerForProtocol(protocol));
+ } catch (MalformedURLException e) {
+ return url;
+ }
+ }
+
+ @Nullable
+ private static URLStreamHandler handlerForProtocol(String protocol) {
+ try {
+ Class> handlerClass = Class.forName(String.format("sun.net.www.protocol.%s.Handler", protocol));
+ return (URLStreamHandler) handlerClass.getDeclaredConstructor().newInstance();
+ } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("unable to create default HTTP stream handler for protocol '{}", protocol, e);
+ } else {
+ log.warn("unable to create default HTTP stream handler for protocol '{}", protocol);
+ }
+ return null;
+ }
+ }
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/HttpUtilsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/HttpUtilsTest.java
index d5fcb3c0aa..8dda5b3874 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/HttpUtilsTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/HttpUtilsTest.java
@@ -18,18 +18,40 @@
*/
package co.elastic.apm.agent.report;
+import co.elastic.apm.agent.sdk.DynamicTransformer;
+import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
+import co.elastic.apm.agent.util.CustomEnvVariables;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
-
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static net.bytebuddy.matcher.ElementMatchers.isStatic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-class HttpUtilsTest {
+class HttpUtilsTest extends CustomEnvVariables {
@Test
void consumeAndCloseIgnoresNullConnection() {
@@ -82,4 +104,115 @@ private static InputStream mockEmptyInputStream() throws IOException {
return stream;
}
+ private static final String FACTORY_MSG = "fake handler can't open connections";
+ private static final AtomicBoolean factoryActive = new AtomicBoolean(false);
+
+ static {
+ // This method to set the global factory is designed to be called once. It is not possible to
+ // un-register the factory after registration
+ URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
+
+ @Nullable
+ @Override
+ public URLStreamHandler createURLStreamHandler(String p) {
+ if (!factoryActive.get()) {
+ // The factory will return null when it does not support a given protocol
+ return null;
+ }
+
+ // any non-null value returned here will be registered globally
+ // In order to reset the default behavior calling URL.setURLStreamHandlerFactory(null) is required.
+ return new URLStreamHandler() {
+ @Override
+ protected URLConnection openConnection(URL u) throws IOException {
+ throw new IllegalStateException(FACTORY_MSG);
+ }
+ };
+ }
+ });
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "https"})
+ void nonDefaultUrlHandler(String protocol) throws IOException {
+
+ // the handler is set in the URL constructor, thus we have to rebuild it
+ // every time we expect a different handler.
+ String url = protocol + "://not.found:9999";
+
+ DynamicTransformer.ensureInstrumented(URL.class, Collections.singleton(TestUrlInstrumentation.class));
+
+ try {
+ // test default behavior
+ checkDefaultHandler(new URL(url));
+
+ factoryActive.set(true);
+
+ // required to remove registered handlers
+ URL.setURLStreamHandlerFactory(null);
+
+ // overridden default handler does not allow opening a connection
+ assertThatThrownBy(() -> new URL(url).openConnection())
+ .hasMessage(FACTORY_MSG);
+
+ checkDefaultHandler(HttpUtils.withDefaultHandler(new URL(url)));
+ } finally {
+ factoryActive.set(false);
+
+ // required to remove registered handlers
+ URL.setURLStreamHandlerFactory(null);
+
+ // should not impact non-fake URLs
+ checkDefaultHandler(new URL(url));
+ }
+ }
+
+
+ private void checkDefaultHandler(URL url) {
+ // unknown host exception is expected here
+ assertThatThrownBy(() -> url.openConnection().getInputStream())
+ .isInstanceOf(UnknownHostException.class);
+ }
+
+ /**
+ * Instrumentation of {@link URL#setURLStreamHandlerFactory(URLStreamHandlerFactory)} to allow resetting handlers
+ * by calling {@code URL.setURLStreamHandlerFactory(null);}.
+ */
+ public static final class TestUrlInstrumentation extends ElasticApmInstrumentation {
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named("java.net.URL");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return isStatic().and(named("setURLStreamHandlerFactory"));
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singleton("test-url");
+ }
+
+ @Override
+ public String getAdviceClassName() {
+ return "co.elastic.apm.agent.report.HttpUtilsTest$TestUrlInstrumentation$AdviceClass";
+ }
+
+ public static final class AdviceClass {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false, skipOn = Advice.OnNonDefaultValue.class)
+ public static boolean onEnter(@Advice.Argument(0) @Nullable URLStreamHandlerFactory factory,
+ @Advice.FieldValue("handlers") Hashtable, ?> handlers) {
+
+ if (factory == null) {
+ handlers.clear();
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CustomEnvVariables.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CustomEnvVariables.java
index 9930badee6..c25c41e06d 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CustomEnvVariables.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CustomEnvVariables.java
@@ -20,13 +20,10 @@
import co.elastic.apm.agent.AbstractInstrumentationTest;
import co.elastic.apm.agent.testinstr.SystemSingleEnvVariablesInstrumentation;
-import org.junit.jupiter.api.Test;
import java.util.Map;
import java.util.concurrent.Callable;
-import static org.assertj.core.api.Assertions.assertThat;
-
public abstract class CustomEnvVariables extends AbstractInstrumentationTest {
protected void runWithCustomEnvVariables(Map customEnvVariables, Runnable runnable) {
From 8a02a416cd50e97d9ff754d6aa5c6274f5a5ac84 Mon Sep 17 00:00:00 2001
From: jackshirazi
Date: Tue, 1 Feb 2022 13:49:04 +0000
Subject: [PATCH 22/46] Speedup instrumentation by not validating (only needed
in tests) (#2439)
---
.../main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 7cd988b920..a8b144686e 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -427,7 +427,11 @@ private static Logger getLogger() {
}
private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticApmInstrumentation instrumentation, final Logger logger, final ElementMatcher super MethodDescription> methodMatcher) {
- validateAdvice(instrumentation);
+ boolean validate = false;
+ assert validate = true;
+ if (validate) {
+ validateAdvice(instrumentation);
+ }
Advice.WithCustomMapping withCustomMapping = Advice
.withCustomMapping()
.with(new Advice.AssignReturned.Factory().withSuppressed(ClassCastException.class))
From 2ddd4cdd0d0755749961eb9c337372d0a1f94f9b Mon Sep 17 00:00:00 2001
From: Nugusbayev Kanagat <48118512+kananindzya@users.noreply.github.com>
Date: Tue, 1 Feb 2022 20:18:34 +0600
Subject: [PATCH 23/46] Extend tests for webflux (#2159)
* added reactor plugin to agent dependencies. Deleted Mono#log for avoid cast class exception with trace log level(class co.elastic.apm.agent.springwebflux.TransactionAwareSubscriber cannot be cast to class reactor.core.Fuseable)
* local
* added security to test application. added tests with secured endpoints
* minor fixes
* delete imports
* added check on null for InetSocketAddress#getAddress
* minor cleanup
Co-authored-by: Sylvain Juge
---
.../AbstractServerInstrumentationTest.java | 85 +++++++++++++++++++
.../apm-spring-webflux-testapp/pom.xml | 5 ++
.../testapp/GreetingAnnotated.java | 19 +++++
.../testapp/GreetingFunctional.java | 10 +++
.../testapp/GreetingHandler.java | 6 ++
.../testapp/GreetingWebClient.java | 7 ++
.../springwebflux/testapp/WebFluxConfig.java | 32 +++++++
7 files changed, 164 insertions(+)
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java
index f52c15be0e..c6f90ee0a1 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/AbstractServerInstrumentationTest.java
@@ -23,6 +23,7 @@
import co.elastic.apm.agent.impl.context.Request;
import co.elastic.apm.agent.impl.context.Url;
import co.elastic.apm.agent.impl.context.web.WebConfiguration;
+import co.elastic.apm.agent.impl.error.ErrorCapture;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.matcher.WildcardMatcher;
import co.elastic.apm.agent.springwebflux.testapp.GreetingWebClient;
@@ -51,6 +52,9 @@
public abstract class AbstractServerInstrumentationTest extends AbstractInstrumentationTest {
+ // 'elastic:changeme' in base64
+ private static final String BASIC_AUTH_HEADER_VALUE = "Basic ZWxhc3RpYzpjaGFuZ2VtZQ==";
+
protected static WebFluxApplication.App app;
protected GreetingWebClient client;
@@ -194,6 +198,8 @@ private Predicate expectClientError(int expectedStatus) {
@ParameterizedTest
@CsvSource({"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"})
void methodMapping(String method) {
+ client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE);
+
var verifier = StepVerifier.create(client.methodMapping(method));
if ("HEAD".equals(method)) {
verifier.verifyComplete();
@@ -290,6 +296,81 @@ void childSpansServerSideEvents_shouldNotCreateTransaction() {
// reporter.assertNoSpan(200);
}
+ @Test
+ void testPreauthorized_shouldSuccessWithAuthorizationHeader() {
+ client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE);
+
+ StepVerifier.create(client.getPreAuthorized(200))
+ .expectNext("Hello, elastic!")
+ .verifyComplete();
+
+ String expectedName = client.useFunctionalEndpoint()
+ ? "GET /functional/preauthorized"
+ : "GreetingAnnotated#getPreauthorized";
+ checkTransaction(getFirstTransaction(), expectedName, "GET", 200);
+ }
+
+ @Test
+ void testPreauthorized_shouldFailWithoutAuthorization() {
+ int expectedStatusCode = 500;
+ boolean checkTransaction = true;
+ Class expectedExceptionClass = WebClientResponseException.InternalServerError.class;
+ if (client.useFunctionalEndpoint()) {
+ expectedStatusCode = 200;
+ expectedExceptionClass = IllegalStateException.class;
+ checkTransaction = false;
+ }
+ StepVerifier.create(client.getPreAuthorized(expectedStatusCode))
+ .expectError(expectedExceptionClass)
+ .verify();
+
+ if (checkTransaction) {
+ String expectedName = client.useFunctionalEndpoint()
+ ? "GET /functional/preauthorized"
+ : "GreetingAnnotated#getPreauthorized";
+ checkTransaction(getFirstTransaction(), expectedName, "GET", expectedStatusCode);
+ } else {
+ // when functional - transaction not created.
+ }
+ }
+
+ @Test
+ void testSecurityContext_shouldSuccessWithAuthorizationHeader() {
+ client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE);
+
+ StepVerifier.create(client.getSecurityContextUsername(200))
+ .expectNext("elastic")
+ .verifyComplete();
+
+ String expectedName = client.useFunctionalEndpoint()
+ ? "GET /functional/username"
+ : "GreetingAnnotated#getSecurityContextUsername";
+ checkTransaction(getFirstTransaction(), expectedName, "GET", 200);
+ }
+
+ @Test
+ void testSecurityContextByPath_shouldSuccessWithAuthorizationHeader() {
+ client.setHeader("Authorization", BASIC_AUTH_HEADER_VALUE);
+
+ StepVerifier.create(client.getSecurityContextUsernameByPathSecured(200))
+ .expectNext("elastic")
+ .verifyComplete();
+
+ String expectedName = client.useFunctionalEndpoint()
+ ? "GET /functional/path-username"
+ : "GreetingAnnotated#getSecurityContextUsernameByPathSecured";
+ checkTransaction(getFirstTransaction(), expectedName, "GET", 200);
+ }
+
+ @Test
+ void testSecurityContextByPath_shouldFailWithoutAuthorizationHeader() {
+ StepVerifier.create(client.getSecurityContextUsernameByPathSecured(401))
+ .expectError(WebClientResponseException.Unauthorized.class)
+ .verify();
+
+ // no transactions, not errors captured.
+ }
+
private static Predicate> checkSSE(final int index) {
return sse -> {
String data = sse.data();
@@ -341,6 +422,10 @@ protected Transaction getFirstTransaction() {
return reporter.getFirstTransaction(200);
}
+ protected ErrorCapture getFirstError() {
+ return reporter.getFirstError(200);
+ }
+
static Transaction checkTransaction(Transaction transaction, String expectedName, String expectedMethod, int expectedStatus) {
assertThat(transaction.getType()).isEqualTo("request");
assertThat(transaction.getNameAsString()).isEqualTo(expectedName);
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml
index c3746bb2ec..2730d43ad1 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml
@@ -60,6 +60,11 @@
spring-boot-starter-tomcat
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
com.fasterxml.jackson.core
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java
index 6c37dc8aa3..785941cbd9 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java
@@ -29,6 +29,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
@@ -67,6 +68,24 @@ public Mono getHello(@RequestParam(value = "name", required = false) @Nu
return greetingHandler.helloMessage(name);
}
+ @PreAuthorize("hasAuthority('ROLE_USER')")
+ @RequestMapping("/preauthorized")
+ public Mono getPreauthorized() {
+ return greetingHandler.helloMessage("elastic");
+ }
+
+ @PreAuthorize("hasAuthority('ROLE_USER')")
+ @RequestMapping("/username")
+ public Mono getSecurityContextUsername() {
+ return greetingHandler.getUsernameFromContext();
+ }
+
+ // protected by SecurityWebFilterChain#pathMatchers
+ @RequestMapping("/path-username")
+ public Mono getSecurityContextUsernameByPathSecured() {
+ return greetingHandler.getUsernameFromContext();
+ }
+
@RequestMapping("/error-handler")
public Mono handlerError() {
// using delayed exception here allows to ensure that the exception handler is properly
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java
index 27f771ca73..8c3a70aadb 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingFunctional.java
@@ -53,6 +53,12 @@ public RouterFunction route(GreetingHandler greetingHandler) {
// 'hello' and 'hello2' are identical, but entry point in builder is not
.route(path("/functional/hello"),
request -> helloGreeting(greetingHandler, request.queryParam("name")))
+ .route(path("/functional/preauthorized"),
+ request -> helloGreeting(greetingHandler, Optional.of("elastic")))
+ .route(path("/functional/username"),
+ request -> getUsernameFromContext(greetingHandler))
+ .route(path("/functional/path-username"),
+ request -> getUsernameFromContext(greetingHandler))
//
.GET("/functional/hello2", accept(MediaType.TEXT_PLAIN),
request -> helloGreeting(greetingHandler, request.queryParam("name")))
@@ -120,6 +126,10 @@ private Mono helloGreeting(GreetingHandler greetingHandler, Opti
return response(greetingHandler.helloMessage(name.orElse(null)));
}
+ private Mono getUsernameFromContext(GreetingHandler greetingHandler) {
+ return response(greetingHandler.getUsernameFromContext());
+ }
+
private Mono response(Mono value) {
return value.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java
index 7d65311e04..e27fdf3874 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingHandler.java
@@ -21,6 +21,7 @@
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.impl.transaction.Span;
import org.springframework.http.codec.ServerSentEvent;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -94,6 +95,11 @@ public Mono duration(long durationMillis) {
.doOnNext(m -> fakeWork(durationMillis));
}
+ public Mono getUsernameFromContext() {
+ return ReactiveSecurityContextHolder.getContext()
+ .flatMap(ctx -> Mono.just(ctx.getAuthentication().getName()));
+ }
+
private static void fakeWork(long durationMs) {
try {
Thread.sleep(durationMs);
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java
index ba7d889758..4f74531c75 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java
@@ -84,6 +84,12 @@ public Mono getHelloMono() {
return requestMono("GET", "/hello", 200);
}
+ public Mono getPreAuthorized(int expectedStatus) { return requestMono("GET", "/preauthorized", expectedStatus); }
+
+ public Mono getSecurityContextUsername(int expectedStatus) { return requestMono("GET", "/username", expectedStatus); }
+
+ public Mono getSecurityContextUsernameByPathSecured(int expectedStatus) { return requestMono("GET", "/path-username", expectedStatus); }
+
public Mono getMappingError404() {
return requestMono("GET", "/error-404", 404);
}
@@ -155,6 +161,7 @@ public Mono customTransactionName() {
public Mono requestMono(String method, String path, int expectedStatus) {
Mono request = request(method, path, expectedStatus)
.bodyToMono(String.class)
+ .doOnError((throwable) -> logger.error("Exception occurred during requesting with exception class [{}]", throwable.getClass().getCanonicalName()))
.publishOn(clientScheduler);
return logEnabled ? request.log(logger) : request;
}
diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java
index af75a8d8ad..dcf8a485b3 100644
--- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java
+++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java
@@ -32,6 +32,12 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorResourceFactory;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@@ -51,6 +57,8 @@
@Configuration
@EnableWebFlux
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
public class WebFluxConfig implements WebFluxConfigurer {
// those server methods are just plain copies of ReactiveWebServerFactoryConfiguration inner classes
@@ -141,4 +149,28 @@ public WebSocketService webSocketService(@Qualifier("requestUpdateStrategy") Req
return new HandshakeWebSocketService(upgradeStrategy);
}
+ @Bean
+ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ return http
+ .csrf()
+ .disable()
+ .authorizeExchange()
+ .pathMatchers("/annotated/path-username").hasAnyAuthority("ROLE_USER")
+ .pathMatchers("/functional/path-username", "/functional/username", "/functional/preauthorized").hasAnyAuthority("ROLE_USER")
+ .pathMatchers("/**").permitAll()
+ .and()
+ .httpBasic()
+ .and()
+ .build();
+ }
+
+ @Bean
+ public MapReactiveUserDetailsService userDetailsService() {
+ return new MapReactiveUserDetailsService(
+ User.withDefaultPasswordEncoder()
+ .username("elastic")
+ .password("changeme")
+ .roles("USER")
+ .build());
+ }
}
From e9525aadcfee43faaf575a46c857341602513d73 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Wed, 2 Feb 2022 10:23:27 +0100
Subject: [PATCH 24/46] Service name discovery based on webapp manifest (#2434)
---
CHANGELOG.asciidoc | 15 +-
.../apm/agent/configuration/ServiceInfo.java | 104 +++++--
.../apm/agent/impl/ElasticApmTracer.java | 16 +-
.../elastic/apm/agent/impl/GlobalTracer.java | 10 +-
.../co/elastic/apm/agent/impl/NoopTracer.java | 7 +-
.../co/elastic/apm/agent/impl/Tracer.java | 18 +-
.../agent/configuration/ServiceInfoTest.java | 100 ++++++-
.../apm/agent/impl/ElasticApmTracerTest.java | 46 ++-
.../serialize/MetricRegistryReporterTest.java | 3 +-
.../serialize/MetricSetSerializationTest.java | 4 +-
.../api/ElasticApmApiInstrumentationTest.java | 30 +-
.../agent/servlet/AbstractServletTest.java | 2 +-
.../JakartaServletInstrumentationTest.java | 1 -
.../InitServiceNameInstrumentation.java | 128 ++++++++
.../servlet/JakartaServletApiAdvice.java | 225 +-------------
.../agent/servlet/JavaxServletApiAdvice.java | 226 +-------------
.../apm/agent/servlet/ServletApiAdvice.java | 113 ++++---
.../servlet/ServletServiceNameHelper.java | 98 ++++++
.../servlet/ServletTransactionHelper.java | 60 ++--
.../agent/servlet/adapter/FilterAdapter.java | 26 ++
.../adapter/JakartaServletApiAdapter.java | 283 ++++++++++++++++++
.../adapter/JavaxServletApiAdapter.java | 283 ++++++++++++++++++
.../ServletAdapter.java} | 14 +-
.../servlet/adapter/ServletApiAdapter.java | 30 ++
.../adapter/ServletContextAdapter.java | 38 +++
.../ServletRequestAdapter.java} | 48 +--
.../ServletRequestResponseAdapter.java | 27 ++
.../adapter/ServletResponseAdapter.java | 40 +++
...> AbstractServletRequestHeaderGetter.java} | 10 +-
.../JakartaServletRequestHeaderGetter.java | 8 +-
...kartaServletTransactionCreationHelper.java | 70 -----
.../JavaxServletRequestHeaderGetter.java | 8 +-
...JavaxServletTransactionCreationHelper.java | 71 -----
.../ServletTransactionCreationHelper.java | 112 -------
...ic.apm.agent.sdk.ElasticApmInstrumentation | 2 +
.../agent/servlet/AbstractServletTest.java | 2 +-
.../agent/servlet/CustomManifestLoader.java | 58 ++++
.../InitServiceNameInstrumentationTest.java | 89 ++++++
.../servlet/ServletInstrumentationTest.java | 1 -
.../servlet/ServletServiceNameHelperTest.java | 87 ++++++
.../servlet/ServletTransactionHelperTest.java | 24 --
.../servlet/helper/ServletApiAdapterTest.java | 48 +++
.../ServletTransactionCreationHelperTest.java | 34 +--
.../src/test/resources/TEST-MANIFEST.MF | 2 +
...rollerTransactionNameInstrumentation.java} | 2 +-
.../SpringServiceNameInstrumentation.java | 7 +-
...ic.apm.agent.sdk.ElasticApmInstrumentation | 2 +-
...stractServletContainerIntegrationTest.java | 23 +-
.../AbstractJsfApplicationServerTestApp.java | 2 +-
.../tests/AbstractServletApiTestApp.java | 4 +-
.../tests/CdiApplicationServerTestApp.java | 3 +-
.../CdiJakartaeeApplicationServerTestApp.java | 3 +-
.../CdiJakartaeeServletContainerTestApp.java | 3 +-
.../tests/CdiServletContainerTestApp.java | 3 +-
.../servlet/tests/ExternalPluginTestApp.java | 7 +-
.../JakartaeeJsfServletContainerTestApp.java | 3 +-
.../tests/JakartaeeServletApiTestApp.java | 2 +-
.../tests/JsfServletContainerTestApp.java | 3 +-
.../apm/servlet/tests/ServletApiTestApp.java | 2 +-
.../apm/servlet/tests/SoapTestApp.java | 3 +-
.../co/elastic/apm/servlet/tests/TestApp.java | 9 +-
.../jakartaee-simple-webapp/pom.xml | 13 +
integration-tests/simple-webapp/pom.xml | 13 +
63 files changed, 1719 insertions(+), 1009 deletions(-)
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentation.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/FilterAdapter.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JakartaServletApiAdapter.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/JavaxServletApiAdapter.java
rename apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/{ServletGlobalState.java => adapter/ServletAdapter.java} (65%)
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletApiAdapter.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletContextAdapter.java
rename apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/{ServletHelper.java => adapter/ServletRequestAdapter.java} (62%)
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletRequestResponseAdapter.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/adapter/ServletResponseAdapter.java
rename apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/{CommonServletRequestHeaderGetter.java => AbstractServletRequestHeaderGetter.java} (79%)
delete mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JakartaServletTransactionCreationHelper.java
delete mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/JavaxServletTransactionCreationHelper.java
delete mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/CustomManifestLoader.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentationTest.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ServletServiceNameHelperTest.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/helper/ServletApiAdapterTest.java
create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/test/resources/TEST-MANIFEST.MF
rename apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/springwebmvc/{ServletWrappingControllerServiceNameInstrumentation.java => ServletWrappingControllerTransactionNameInstrumentation.java} (96%)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index ef8ecbaa63..20a539690a 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
+ *