diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 9d18b00606..877fa2cfb0 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,7 @@ endif::[] [float] ===== Features +* Added support for setting service name and version for a transaction via the public api - {pull}2451[#2451] [[release-notes-1.x]] === Java Agent version 1.x diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/ElasticApm.java b/apm-agent-api/src/main/java/co/elastic/apm/api/ElasticApm.java index 640299fcac..0610be92b4 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/ElasticApm.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/ElasticApm.java @@ -232,4 +232,21 @@ public static void captureException(@Nullable Throwable e) { // co.elastic.apm.api.ElasticApmInstrumentation.CaptureExceptionInstrumentation.captureException } + /** + * Associates a class loader with a service name and version. + *

+ * The association is used to overwrite the autodetected service name and version when a transaction is started. + *

+ *

+ * NOTE: If the class loader already is associated with a service name and version, + * the existing information will not be overwritten. + *

+ * + * @param classLoader the class loader which should be associated with the given service name and version + * @param serviceName the service name + * @param serviceVersion the service version + */ + public static void setServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) { + // co.elastic.apm.api.ElasticApmInstrumentation.SetServiceInfoForClassLoader.setServiceInfoForClassLoader + } } diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java b/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java index fe66caf0da..c7bb42f43c 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java @@ -48,6 +48,20 @@ public Transaction setFrameworkName(String frameworkName) { return this; } + @Nonnull + @Override + public Transaction setServiceInfo(String serviceName, String serviceVersion) { + // noop + return this; + } + + @Nonnull + @Override + public Transaction useServiceInfoForClassLoader(ClassLoader classLoader) { + // noop + return this; + } + @Nonnull @Deprecated @Override diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java b/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java index a4a4f1d4ee..c8c9e9320b 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java @@ -67,6 +67,33 @@ public interface Transaction extends Span { @Nonnull Transaction setFrameworkName(String frameworkName); + /** + * Sets the service name and version for this transaction and its child spans. + *

+ * NOTE: If this method is called after child spans are already created, + * they may have the wrong service name and version. + *

+ * + * @param serviceName the service name + * @param serviceVersion the service version + */ + @Nonnull + Transaction setServiceInfo(String serviceName, String serviceVersion); + + /** + * Sets the service name and version, that are associated with the given class loader + * (see: {@link ElasticApm#setServiceInfoForClassLoader(ClassLoader, String, String)}), + * for this transaction and its child spans. + *

+ * NOTE: If this method is called after child spans are already created, + * they may have the wrong service name and version. + *

+ * + * @param classLoader the class loader that should be used to set the service name and version + */ + @Nonnull + Transaction useServiceInfoForClassLoader(ClassLoader classLoader); + /** * {@inheritDoc} * diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java b/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java index da0ce5bfe5..c990a0340a 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java @@ -56,6 +56,20 @@ public Transaction setType(String type) { return this; } + @Nonnull + @Override + public Transaction setServiceInfo(String serviceName, String serviceVersion) { + // co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetServiceInfoInstrumentation + return this; + } + + @Nonnull + @Override + public Transaction useServiceInfoForClassLoader(ClassLoader classLoader) { + // co.elastic.apm.agent.pluginapi.TransactionInstrumentation$UseServiceInfoForClassLoaderInstrumentation + return this; + } + @Nonnull @Deprecated @Override 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 e7da0791ea..8f8c426ae8 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 @@ -231,10 +231,9 @@ private void afterTransactionStart(@Nullable ClassLoader initiatingClassLoader, new RuntimeException("this exception is just used to record where the transaction has been started from")); } } - final ServiceInfo serviceInfo = getServiceInfo(initiatingClassLoader); + final ServiceInfo serviceInfo = getServiceInfoForClassLoader(initiatingClassLoader); if (serviceInfo != null) { - transaction.getTraceContext().setServiceName(serviceInfo.getServiceName()); - transaction.getTraceContext().setServiceVersion(serviceInfo.getServiceVersion()); + transaction.getTraceContext().setServiceInfo(serviceInfo.getServiceName(), serviceInfo.getServiceVersion()); } } @@ -344,10 +343,9 @@ private ErrorCapture captureException(long epochMicros, @Nullable Throwable e, @ parent.setNonDiscardable(); } else { error.getTraceContext().getId().setToRandomValue(); - ServiceInfo serviceInfo = getServiceInfo(initiatingClassLoader); + ServiceInfo serviceInfo = getServiceInfoForClassLoader(initiatingClassLoader); if (serviceInfo != null) { - error.getTraceContext().setServiceName(serviceInfo.getServiceName()); - error.getTraceContext().setServiceVersion(serviceInfo.getServiceVersion()); + error.getTraceContext().setServiceInfo(serviceInfo.getServiceName(), serviceInfo.getServiceVersion()); } } return error; @@ -750,7 +748,7 @@ public List getServiceInfoOverrides() { } @Override - public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { + public void setServiceInfoForClassLoader(@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 @@ -767,7 +765,8 @@ public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, } @Nullable - public ServiceInfo getServiceInfo(@Nullable ClassLoader initiatingClassLoader) { + @Override + public ServiceInfo getServiceInfoForClassLoader(@Nullable ClassLoader initiatingClassLoader) { if (initiatingClassLoader == null) { return null; } 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 c7b72ea4fc..927b9c2655 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,9 +204,15 @@ public TracerState getState() { return tracer.getState(); } + @Nullable + @Override + public ServiceInfo getServiceInfoForClassLoader(@Nullable ClassLoader classLoader) { + return tracer.getServiceInfoForClassLoader(classLoader); + } + @Override - public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { - tracer.overrideServiceInfoForClassLoader(classLoader, serviceInfo); + public void setServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { + tracer.setServiceInfoForClassLoader(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 4621a45b18..b0e35cdf76 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,8 +130,14 @@ public TracerState getState() { return TracerState.UNINITIALIZED; } + @Nullable + @Override + public ServiceInfo getServiceInfoForClassLoader(@Nullable ClassLoader classLoader) { + return null; + } + @Override - public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) { + public void setServiceInfoForClassLoader(@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 ade46e7c7a..c4b23583b8 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 @@ -153,8 +153,11 @@ Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet TracerState getState(); + @Nullable + ServiceInfo getServiceInfoForClassLoader(@Nullable ClassLoader classLoader); + /** - * Overrides the service name and version for all {@link Transaction}s, + * Sets 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. @@ -163,7 +166,7 @@ Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet * @param classLoader the class loader which corresponds to a particular service * @param serviceInfo the service name and version for this class loader */ - void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo); + void setServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo); /** * Called when the container shuts down. diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index 455e6272b6..6f2393936f 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 @@ -682,12 +682,17 @@ public String getServiceName() { } /** - * Overrides the {@code co.elastic.apm.agent.impl.payload.Service#name} property sent via the meta data Intake V2 event. + * Overrides the {@code co.elastic.apm.agent.impl.payload.Service#name} and {@code co.elastic.apm.agent.impl.payload.Service#version} properties sent via the meta data Intake V2 event. * - * @param serviceName the service name for this event + * @param serviceName the service name for this event + * @param serviceVersion the service version for this event */ - public void setServiceName(@Nullable String serviceName) { + public void setServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) { + if (serviceName == null || serviceName.isEmpty()) { + return; + } this.serviceName = serviceName; + this.serviceVersion = serviceVersion; } @Nullable @@ -695,15 +700,6 @@ 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); } 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 113f9328c4..e38466c8de 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 @@ -434,8 +434,8 @@ void testOverrideServiceNameWithoutExplicitServiceName() { ClassLoader cl = getClass().getClassLoader(); ServiceInfo overridden = ServiceInfo.of("overridden"); - tracer.overrideServiceInfoForClassLoader(cl, overridden); - assertThat(tracer.getServiceInfo(cl)).isEqualTo(overridden); + tracer.setServiceInfoForClassLoader(cl, overridden); + assertThat(tracer.getServiceInfoForClassLoader(cl)).isEqualTo(overridden); startTestRootTransaction().end(); @@ -452,8 +452,8 @@ void testNotOverrideServiceNameWhenServiceNameConfigured() { .configurationRegistry(localConfig) .buildAndStart(); ClassLoader cl = getClass().getClassLoader(); - tracer.overrideServiceInfoForClassLoader(cl, ServiceInfo.of("overridden")); - assertThat(tracer.getServiceInfo(cl)).isNull(); + tracer.setServiceInfoForClassLoader(cl, ServiceInfo.of("overridden")); + assertThat(tracer.getServiceInfoForClassLoader(cl)).isNull(); startTestRootTransaction().end(); @@ -474,8 +474,8 @@ void testNotOverrideServiceNameWhenDefaultServiceNameConfigured() { .buildAndStart(); ClassLoader cl = getClass().getClassLoader(); - tracer.overrideServiceInfoForClassLoader(cl, ServiceInfo.of("overridden")); - assertThat(tracer.getServiceInfo(cl)).isNull(); + tracer.setServiceInfoForClassLoader(cl, ServiceInfo.of("overridden")); + assertThat(tracer.getServiceInfoForClassLoader(cl)).isNull(); startTestRootTransaction().end(); @@ -505,7 +505,7 @@ void testOverrideServiceVersionWithoutExplicitServiceVersion() { .buildAndStart(); ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); - tracer.overrideServiceInfoForClassLoader(getClass().getClassLoader(), overridden); + tracer.setServiceInfoForClassLoader(getClass().getClassLoader(), overridden); startTestRootTransaction().end(); @@ -522,8 +522,8 @@ void testNotOverrideServiceVersionWhenServiceVersionConfigured() { ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); ClassLoader cl = getClass().getClassLoader(); - tracer.overrideServiceInfoForClassLoader(cl, overridden); - assertThat(tracer.getServiceInfo(cl)).isEqualTo(overridden); + tracer.setServiceInfoForClassLoader(cl, overridden); + assertThat(tracer.getServiceInfoForClassLoader(cl)).isEqualTo(overridden); startTestRootTransaction().end(); 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 d6eabb05d8..31c724d116 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 @@ -348,7 +348,7 @@ void testBreakdown_spanStartedAfterParentEnded() { @Test void testBreakdown_serviceName() { final Transaction transaction = createTransaction(); - transaction.getTraceContext().setServiceName("service_name"); + transaction.getTraceContext().setServiceInfo("service_name", null); transaction.createSpan(11).withType("db").withSubtype("mysql").end(23); transaction.end(27); @@ -363,8 +363,7 @@ void testBreakdown_serviceName() { @Test void testBreakdown_serviceNameAndVersion() { final Transaction transaction = createTransaction(); - transaction.getTraceContext().setServiceName("service_name"); - transaction.getTraceContext().setServiceVersion("service_version"); + transaction.getTraceContext().setServiceInfo("service_name", "service_version"); transaction.createSpan(11).withType("db").withSubtype("mysql").end(23); transaction.end(27); 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 b53de50530..e9512b7d98 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 @@ -744,4 +744,27 @@ void testDeserialization() { assertThat(deserialized.getClock().getOffset()).isEqualTo(traceContext.getClock().getOffset()); } + @Test + void testSetServiceInfoWithEmptyServiceName() { + TraceContext traceContext = TraceContext.with64BitId(tracer); + + traceContext.setServiceInfo(null, "My Version"); + assertThat(traceContext.getServiceName()).isNull(); + assertThat(traceContext.getServiceVersion()).isNull(); + traceContext.setServiceInfo("", "My Version"); + assertThat(traceContext.getServiceName()).isNull(); + assertThat(traceContext.getServiceVersion()).isNull(); + } + + @Test + void testSetServiceInfoWithNonEmptyServiceName() { + TraceContext traceContext = TraceContext.with64BitId(tracer); + + traceContext.setServiceInfo("My Service", null); + assertThat(traceContext.getServiceName()).isEqualTo("My Service"); + assertThat(traceContext.getServiceVersion()).isNull(); + traceContext.setServiceInfo("My Service", "My Version"); + assertThat(traceContext.getServiceName()).isEqualTo("My Service"); + assertThat(traceContext.getServiceVersion()).isEqualTo("My Version"); + } } 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 d140d0471b..b6dd633e3b 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 @@ -441,7 +441,7 @@ void testSpanDestinationContextSerialization() { @Test void testTransactionNullFrameworkNameSerialization() { Transaction transaction = new Transaction(MockTracer.create()); - transaction.getTraceContext().setServiceName("service-name"); + transaction.getTraceContext().setServiceInfo("service-name", null); transaction.setUserFrameworkName(null); JsonNode transactionJson = readJsonString(serializer.toJsonString(transaction)); assertThat(transactionJson.get("context").get("service").get("framework")).isNull(); @@ -450,7 +450,7 @@ void testTransactionNullFrameworkNameSerialization() { @Test void testTransactionEmptyFrameworkNameSerialization() { Transaction transaction = new Transaction(MockTracer.create()); - transaction.getTraceContext().setServiceName("service-name"); + transaction.getTraceContext().setServiceInfo("service-name", null); transaction.setUserFrameworkName(""); JsonNode transactionJson = readJsonString(serializer.toJsonString(transaction)); assertThat(transactionJson.get("context").get("service").get("framework")).isNull(); @@ -827,8 +827,7 @@ void testTransactionContextSerialization() { String frameworkName = RandomStringUtils.randomAlphanumeric(10); String frameworkVersion = RandomStringUtils.randomNumeric(3); - ctx.setServiceName(serviceName); - ctx.setServiceVersion(serviceVersion); + ctx.setServiceInfo(serviceName, serviceVersion); transaction.setFrameworkName(frameworkName); transaction.setFrameworkVersion(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 d965364c59..6f646b0bc5 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 @@ -41,7 +41,7 @@ void testReportedMetricsUseDefaultServiceNameIfServiceNameIsExplicitlySet() thro .configurationRegistry(SpyConfiguration.createSpyConfig(SimpleSource.forTest("service_name", "foo"))) .reporter(reporter) .buildAndStart(); - tracer.overrideServiceInfoForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), ServiceInfo.of("MetricRegistryReporterTest")); + tracer.setServiceInfoForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), ServiceInfo.of("MetricRegistryReporterTest")); new MetricRegistryReporter(tracer).run(); diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/ElasticApmApiInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/ElasticApmApiInstrumentation.java index 471ae0ff02..1ee320425b 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/ElasticApmApiInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/ElasticApmApiInstrumentation.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.pluginapi; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -147,4 +148,16 @@ public static void captureException(@Advice.Origin Class clazz, @Advice.Argum } } + public static class SetServiceInfoForClassLoaderInstrumentation extends ElasticApmApiInstrumentation { + public SetServiceInfoForClassLoaderInstrumentation() { + super(named("setServiceInfoForClassLoader")); + } + + public static class AdviceClass { + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + public static void setServiceInfoForClassLoader(@Advice.Argument(0) @Nullable ClassLoader classLoader, @Advice.Argument(1) String serviceName, @Advice.Argument(2) @Nullable String serviceVersion) { + tracer.setServiceInfoForClassLoader(classLoader, ServiceInfo.of(serviceName, serviceVersion)); + } + } + } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentation.java index d96638fabd..766cf743eb 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentation.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.pluginapi; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.transaction.Id; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; @@ -147,4 +148,39 @@ public static void addCustomContext(@Advice.FieldValue(value = "span", typing = } } } + + public static class SetServiceInfoInstrumentation extends TransactionInstrumentation { + public SetServiceInfoInstrumentation() { + super(named("setServiceInfo")); + } + + public static class AdviceClass { + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void setServiceInfo(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Object transaction, + @Advice.Argument(0) String serviceName, @Advice.Argument(1) String serviceVersion) { + if (transaction instanceof Transaction) { + ((Transaction) transaction).getTraceContext().setServiceInfo(serviceName, serviceVersion); + } + } + } + } + + public static class UseServiceInfoForClassLoaderInstrumentation extends TransactionInstrumentation { + public UseServiceInfoForClassLoaderInstrumentation() { + super(named("useServiceInfoForClassLoader")); + } + + public static class AdviceClass { + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static void useServiceInfoForClassLoader(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Object transaction, + @Advice.Argument(0) ClassLoader classLoader) { + if (transaction instanceof Transaction) { + ServiceInfo serviceInfo = tracer.getServiceInfoForClassLoader(classLoader); + if (serviceInfo != null) { + ((Transaction) transaction).getTraceContext().setServiceInfo(serviceInfo.getServiceName(), serviceInfo.getServiceVersion()); + } + } + } + } + } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 161e9bceca..dd2716fdfd 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -3,11 +3,14 @@ co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$StartTransactionWith co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$CurrentTransactionInstrumentation co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$CurrentSpanInstrumentation co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$CaptureExceptionInstrumentation +co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$SetServiceInfoForClassLoaderInstrumentation co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetFrameworkNameInstrumentation co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetUserInstrumentation co.elastic.apm.agent.pluginapi.TransactionInstrumentation$EnsureParentIdInstrumentation co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetResultInstrumentation co.elastic.apm.agent.pluginapi.TransactionInstrumentation$AddCustomContextInstrumentation +co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetServiceInfoInstrumentation +co.elastic.apm.agent.pluginapi.TransactionInstrumentation$UseServiceInfoForClassLoaderInstrumentation co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$SetNameInstrumentation co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$SetTypeInstrumentation co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$SetTypesInstrumentation diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentationTest.java index cd84e97cba..ab3cdd5202 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/pluginapi/TransactionInstrumentationTest.java @@ -19,6 +19,7 @@ package co.elastic.apm.agent.pluginapi; import co.elastic.apm.AbstractApiTest; +import co.elastic.apm.agent.configuration.ServiceInfo; import co.elastic.apm.agent.impl.TracerInternalApiUtils; import co.elastic.apm.api.AbstractSpanImplAccessor; import co.elastic.apm.api.ElasticApm; @@ -296,6 +297,27 @@ void setOutcome_success() { testSetOutcome(Outcome.SUCCESS); } + @Test + void setSetServiceInfo() { + transaction.setServiceInfo("My Service", "My Version"); + endTransaction(); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("My Service"); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("My Version"); + } + + @Test + void useServiceInfoForClassLoader() { + try { + tracer.setServiceInfoForClassLoader(TransactionInstrumentationTest.class.getClassLoader(), ServiceInfo.of("My Service", "My Version")); + transaction.useServiceInfoForClassLoader(TransactionInstrumentationTest.class.getClassLoader()); + endTransaction(); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("My Service"); + assertThat(reporter.getFirstTransaction().getTraceContext().getServiceVersion()).isEqualTo("My Version"); + } finally { + tracer.resetServiceInfoOverrides(); + } + } + private void testSetOutcome(Outcome outcome) { // set it first to a different value than the expected one Outcome[] values = Outcome.values(); 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 78183567bc..bb4ed04e09 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 @@ -330,7 +330,7 @@ void testManualTimestampsDeactivated() { @Test void testOverrideServiceNameForClassLoader() { ServiceInfo overridden = ServiceInfo.of("overridden"); - tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); + tracer.setServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); ElasticApm.startTransaction().end(); checkTransactionServiceInfo(overridden); } @@ -338,7 +338,7 @@ void testOverrideServiceNameForClassLoader() { @Test void testOverrideServiceNameForClassLoaderWithRemoteParent() { ServiceInfo overridden = ServiceInfo.of("overridden"); - tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); + tracer.setServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); ElasticApm.startTransactionWithRemoteParent(key -> null).end(); checkTransactionServiceInfo(overridden); } @@ -346,7 +346,7 @@ void testOverrideServiceNameForClassLoaderWithRemoteParent() { @Test void testOverrideServiceVersionForClassLoader() { ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); - tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); + tracer.setServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); ElasticApm.startTransaction().end(); checkTransactionServiceInfo(overridden); } @@ -354,7 +354,7 @@ void testOverrideServiceVersionForClassLoader() { @Test void testOverrideServiceVersionForClassLoaderWithRemoteParent() { ServiceInfo overridden = ServiceInfo.of("overridden_name", "overridden_version"); - tracer.overrideServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); + tracer.setServiceInfoForClassLoader(Transaction.class.getClassLoader(), overridden); ElasticApm.startTransactionWithRemoteParent(key -> null).end(); checkTransactionServiceInfo(overridden); } @@ -370,4 +370,16 @@ void testFrameworkNameWithStartTransactionWithRemoteParent() { ElasticApm.startTransactionWithRemoteParent(null).end(); assertThat(reporter.getFirstTransaction().getFrameworkName()).isEqualTo("API"); } + + @Test + void testSetServiceInfo() { + try { + ElasticApm.setServiceInfoForClassLoader(ElasticApmApiInstrumentationTest.class.getClassLoader(), "My Service", "My Version"); + ServiceInfo getServiceInfo = tracer.getServiceInfoForClassLoader(ElasticApmApiInstrumentationTest.class.getClassLoader()); + assertThat(getServiceInfo.getServiceName()).isEqualTo("My Service"); + assertThat(getServiceInfo.getServiceVersion()).isEqualTo("My Version"); + } finally { + tracer.resetServiceInfoOverrides(); + } + } } 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 index ac11702d62..435f8b51b8 100644 --- 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 @@ -65,7 +65,7 @@ public static class AdviceClass { @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()); + ServiceInfo serviceInfo = tracer.getServiceInfoForClassLoader(Thread.currentThread().getContextClassLoader()); String configuredServiceName = tracer.getConfig(CoreConfiguration.class).getServiceName(); builder.setServiceName(serviceInfo != null ? serviceInfo.getServiceName() : configuredServiceName); } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java index 8ed2c4850d..2b00e0fb01 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletServiceNameHelper.java @@ -51,7 +51,7 @@ public static void determineServiceName(ServletContextAdapter ServiceInfo detectServiceInfo(ServletContextAdapter adapter, ServletContext servletContext, ClassLoader servletContextClassLoader) { 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 60c2ff3a97..f7cd9204d8 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 @@ -97,7 +97,7 @@ public static void afterInitPropertySources(@Advice.This WebApplicationContext a ServiceInfo fromSpringApplicationNameProperty = ServiceInfo.of(applicationContext.getEnvironment().getProperty("spring.application.name", "")); ServiceInfo fromApplicationContextApplicationName = ServiceInfo.of(removeLeadingSlash(applicationContext.getApplicationName())); - tracer.overrideServiceInfoForClassLoader(classLoader, fromSpringApplicationNameProperty + tracer.setServiceInfoForClassLoader(classLoader, fromSpringApplicationNameProperty .withFallback(fromServletContext) .withFallback(fromApplicationContextApplicationName)); } diff --git a/docs/public-api.asciidoc b/docs/public-api.asciidoc index 2861f1c11e..54a70cf15d 100644 --- a/docs/public-api.asciidoc +++ b/docs/public-api.asciidoc @@ -192,6 +192,19 @@ public Response onIncomingRequest(Request request) throws Exception { NOTE: If the protocol does not support multi-value headers, use <> +[float] +[[api-set-service-info-for-class-loader]] +==== `void setServiceInfoForClassLoader(ClassLoader, String, String)` added[1.30.0] +Associates a class loader with a service name and version. + +The association is used to overwrite the autodetected service name and version when a transaction is started. + +NOTE: If the class loader already is associated with a service name and version, the existing information will not be overwritten. + +* `classLoader`: the class loader which should be associated with the given service name and version +* `serviceName`: the service name +* `serviceVersion`: the service version + //---------------------------- [float] [[api-annotation]] @@ -329,6 +342,27 @@ transaction.setFrameworkName("My Framework"); * `frameworkName`: The name of the framework +[float] +[[api-transaction-set-service-info]] +==== `Transaction setServiceInfo(String serviceName, String serviceVersion)` added[1.30.0] +Sets the service name and version for this transaction and its child spans. + +NOTE: If this method is called after child spans are already created, they may have the wrong service name and version. + +* `serviceName`: the service name +* `serviceVersion`: the service version + +[float] +[[api-transaction-use-service-info-for-class-loader]] +==== `Transaction useServiceInfoForClassLoader(ClassLoader classLoader)` added[1.30.0] +Sets the service name and version, that are associated with the given class loader +(see: <>), +for this transaction and its child spans. + +NOTE: If this method is called after child spans are already created, they may have the wrong service name and version. + +* `classLoader`: the class loader that should be used to set the service name and version + [float] [[api-transaction-add-tag]] ==== `Transaction setLabel(String key, value)` added[1.5.0 as `addLabel`,Number and boolean labels require APM Server 6.7] 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 5c07b4cc4f..27621fe601 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 @@ -92,7 +92,7 @@ private void executeTest(final AbstractServletContainerIntegrationTest container /** * Since we test custom transaction creation through the external plugin, the service name for this transaction cannot be - * captured through the {@link Tracer#overrideServiceInfoForClassLoader(ClassLoader, ServiceInfo)} mechanism. + * captured through the {@link Tracer#setServiceInfoForClassLoader(ClassLoader, ServiceInfo)} mechanism. */ @Nullable @Override