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 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 metricSets, String timerName, @Nullable String spanType, @Nullable String spanSubType) { - return getTimer(metricSets, timerName, null, spanType, spanSubType); + return getTimer(metricSets, timerName, null, null, spanType, spanSubType); } @Nullable - private Timer getTimer(Map metricSets, String timerName, @Nullable String serviceName, @Nullable String spanType, @Nullable String spanSubType) { + private Timer getTimer(Map metricSets, String timerName, @Nullable String serviceName, @Nullable String serviceVersion, @Nullable String spanType, @Nullable String spanSubType) { final MetricSet metricSet = metricSets.get(Labels.Mutable.of() .serviceName(serviceName) + .serviceVersion(serviceVersion) .transactionName("test") .transactionType("request") .spanType(spanType) diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index 48e9adea29..b53de50530 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -733,7 +733,7 @@ void testDeserialization() { traceContext.serialize(serializedContext); TraceContext deserialized = TraceContext.with64BitId(tracer); - deserialized.deserialize(serializedContext, null); + deserialized.deserialize(serializedContext, null, null); assertThat(deserialized.traceIdAndIdEquals(serializedContext)).isTrue(); assertThat(deserialized.getTraceId()).isEqualTo(traceContext.getTraceId()); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java index 116219e093..e1f2d90b08 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java @@ -38,6 +38,9 @@ void testEqualsHashCode() { assertEqualsHashCode( Labels.Mutable.of().serviceName("foo"), Labels.Mutable.of().serviceName("foo")); + assertEqualsHashCode( + Labels.Mutable.of().serviceVersion("foo"), + Labels.Mutable.of().serviceVersion("foo")); assertEqualsHashCode( Labels.Mutable.of().transactionName("foo"), Labels.Mutable.of().transactionName(new StringBuilder("foo"))); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java index dac0b2d6d9..d140d0471b 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java @@ -823,10 +823,12 @@ void testTransactionContextSerialization() { TraceContext ctx = transaction.getTraceContext(); String serviceName = RandomStringUtils.randomAlphabetic(5); + String serviceVersion = RandomStringUtils.randomAlphabetic(5); String frameworkName = RandomStringUtils.randomAlphanumeric(10); String frameworkVersion = RandomStringUtils.randomNumeric(3); ctx.setServiceName(serviceName); + ctx.setServiceVersion(serviceVersion); transaction.setFrameworkName(frameworkName); transaction.setFrameworkVersion(frameworkVersion); @@ -839,6 +841,7 @@ void testTransactionContextSerialization() { assertThat(jsonContext.get("user").get("email").asText()).isEqualTo("user@email.com"); assertThat(jsonContext.get("user").get("username").asText()).isEqualTo("bob"); assertThat(jsonContext.get("service").get("name").asText()).isEqualTo(serviceName); + assertThat(jsonContext.get("service").get("version").asText()).isEqualTo(serviceVersion); assertThat(jsonContext.get("service").get("framework").get("name").asText()).isEqualTo(frameworkName); assertThat(jsonContext.get("service").get("framework").get("version").asText()).isEqualTo(frameworkVersion); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java index 44e05c6f36..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