From cbd6ee78823fd360639b68e3b35a501cb4d3fad8 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Fri, 8 Sep 2023 22:08:08 +0200 Subject: [PATCH 1/2] Move ServiceInfo to tracer-api within custom tracer submodule. --- .../configuration/CoreConfiguration.java | 7 +- .../agent/impl/AutoDetectedServiceInfo.java | 109 ++++++++++++++++++ .../apm/agent/impl/ElasticApmTracer.java | 12 +- .../co/elastic/apm/agent/impl/Tracer.java | 4 +- .../logging/Log4j2ConfigurationFactory.java | 4 +- .../serialize/MetricRegistryReporter.java | 2 +- .../serialize/MetricRegistrySerializer.java | 2 +- .../agent/configuration/ServiceInfoTest.java | 6 +- .../apm/agent/impl/ElasticApmTracerTest.java | 4 +- .../serialize/MetricRegistryReporterTest.java | 2 +- .../serialize/MetricSetSerializationTest.java | 2 +- .../ElasticApmApiInstrumentation.java | 5 +- .../pluginapi/TransactionInstrumentation.java | 5 +- .../TransactionInstrumentationTest.java | 2 +- .../api/ElasticApmApiInstrumentationTest.java | 2 +- .../agent/ecs_logging/EcsLoggingUtils.java | 6 +- .../servlet/ServletServiceNameHelper.java | 10 +- ...tractSpringServiceNameInstrumentation.java | 12 +- .../tracer/service/ServiceAwareTracer.java | 33 ++++++ .../agent/tracer/service}/ServiceInfo.java | 107 +---------------- .../servlet/tests/ExternalPluginTestApp.java | 2 +- 21 files changed, 195 insertions(+), 143 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java create mode 100644 apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceAwareTracer.java rename {apm-agent-core/src/main/java/co/elastic/apm/agent/configuration => apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service}/ServiceInfo.java (53%) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index fafde04a92..3decdeefc5 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -20,6 +20,8 @@ import co.elastic.apm.agent.bci.ElasticApmAgent; import co.elastic.apm.agent.common.util.WildcardMatcher; +import co.elastic.apm.agent.impl.AutoDetectedServiceInfo; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.matcher.MethodMatcher; import co.elastic.apm.agent.matcher.MethodMatcherValueConverter; @@ -31,6 +33,7 @@ import co.elastic.apm.agent.tracer.configuration.TimeDuration; import co.elastic.apm.agent.tracer.configuration.TimeDurationValueConverter; import co.elastic.apm.agent.tracer.configuration.WildcardMatcherValueConverter; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.ConfigurationOptionProvider; import org.stagemonitor.configuration.converter.AbstractValueConverter; @@ -163,7 +166,7 @@ public class CoreConfiguration extends ConfigurationOptionProvider implements co "NOTE: Service name auto discovery mechanisms require APM Server 7.0+.") .addValidator(RegexValidator.of("^[a-zA-Z0-9 _-]+$", "Your service name \"{0}\" must only contain characters " + "from the ASCII alphabet, numbers, dashes, underscores and spaces")) - .buildWithDefault(ServiceInfo.autoDetected().getServiceName()); + .buildWithDefault(AutoDetectedServiceInfo.autoDetected().getServiceName()); private final ConfigurationOption serviceNodeName = ConfigurationOption.stringOption() .key(SERVICE_NODE_NAME) @@ -203,7 +206,7 @@ public class CoreConfiguration extends ConfigurationOptionProvider implements co "the agent can auto-detect the service version based on the `Implementation-Title` attribute in `META-INF/MANIFEST.MF`.\n" + "See <> on how to set this attribute.\n" + "\n") - .defaultValue(ServiceInfo.autoDetected().getServiceVersion()) + .defaultValue(AutoDetectedServiceInfo.autoDetected().getServiceVersion()) .build(); private final ConfigurationOption hostname = ConfigurationOption.stringOption() diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java new file mode 100644 index 0000000000..006715a8e3 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java @@ -0,0 +1,109 @@ +package co.elastic.apm.agent.impl; + +import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; +import co.elastic.apm.agent.tracer.service.ServiceInfo; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Properties; +import java.util.jar.JarFile; + +public class AutoDetectedServiceInfo { + + private static final String JAR_VERSION_SUFFIX = "-(\\d+\\.)+(\\d+)(.*)?$"; + + private static final ServiceInfo AUTO_DETECTED = AutoDetectedServiceInfo.autoDetect(System.getProperties(), PrivilegedActionUtils.getEnv()); + + public static ServiceInfo autoDetected() { + return AUTO_DETECTED; + } + + public static ServiceInfo autoDetect(Properties sysProperties, Map sysEnv) { + String lambdaFunctionName = sysEnv.get("AWS_LAMBDA_FUNCTION_NAME"); + if (lambdaFunctionName != null) { + return ServiceInfo.of(lambdaFunctionName, sysEnv.get("AWS_LAMBDA_FUNCTION_VERSION")); + } else { + ServiceInfo serviceInfo = createFromSunJavaCommand(sysProperties.getProperty("sun.java.command")); + if (serviceInfo != null) { + return serviceInfo; + } + return ServiceInfo.empty(); + } + } + + @Nullable + private static ServiceInfo createFromSunJavaCommand(@Nullable String command) { + if (command == null) { + return null; + } + command = command.trim(); + String serviceName = getContainerServiceName(command); + if (serviceName != null) { + return ServiceInfo.ofMultiServiceContainer(serviceName); + } + if (command.contains(".jar")) { + return fromJarCommand(command); + } else { + return fromMainClassCommand(command); + } + } + + @Nullable + private static String getContainerServiceName(String command) { + if (command.startsWith("org.apache.catalina.startup.Bootstrap")) { + return "tomcat-application"; + } else if (command.startsWith("org.eclipse.jetty")) { + return "jetty-application"; + } else if (command.startsWith("com.sun.enterprise.glassfish")) { + return "glassfish-application"; + } else if (command.contains("ws-server.jar")) { + return "websphere-application"; + } else if (command.contains("jboss-modules.jar")) { + return "jboss-application"; + } else if (command.contains("weblogic")) { + return "weblogic-application"; + } + return null; + } + + private static ServiceInfo fromJarCommand(String command) { + final String[] commandParts = command.split(" "); + ServiceInfo serviceInfoFromManifest = ServiceInfo.empty(); + ServiceInfo serviceInfoFromJarName = ServiceInfo.empty(); + for (String commandPart : commandParts) { + if (commandPart.endsWith(".jar")) { + try (JarFile jarFile = new JarFile(commandPart)) { + serviceInfoFromManifest = ServiceInfo.fromManifest(jarFile.getManifest()); + } catch (Exception ignored) { + } + + serviceInfoFromJarName = ServiceInfo.of(removeVersionFromJar(removePath(removeJarExtension(commandPart)))); + break; + } + } + return serviceInfoFromManifest.withFallback(serviceInfoFromJarName); + } + + private static String removeJarExtension(String commandPart) { + return commandPart.substring(0, commandPart.indexOf(".jar")); + } + + private static String removePath(String path) { + return path.substring(path.lastIndexOf("/") + 1).substring(path.lastIndexOf("\\") + 1); + } + + private static String removeVersionFromJar(String jarFileName) { + return jarFileName.replaceFirst(JAR_VERSION_SUFFIX, ""); + } + + private static ServiceInfo fromMainClassCommand(String command) { + final String mainClassName; + int indexOfSpace = command.indexOf(' '); + if (indexOfSpace != -1) { + mainClassName = command.substring(0, indexOfSpace); + } else { + mainClassName = command; + } + return ServiceInfo.of(mainClassName.substring(mainClassName.lastIndexOf('.') + 1)); + } +} 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 1b7729ad75..841b40b450 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 @@ -25,7 +25,7 @@ import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.configuration.MetricsConfiguration; import co.elastic.apm.agent.configuration.ServerlessConfiguration; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.configuration.SpanConfiguration; import co.elastic.apm.agent.context.ClosableLifecycleListenerAdapter; import co.elastic.apm.agent.context.LifecycleListener; @@ -74,11 +74,13 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; +import java.util.jar.JarFile; /** @@ -93,6 +95,7 @@ public class ElasticApmTracer implements Tracer { private static final WeakMap serviceInfoByClassLoader = WeakConcurrent.buildMap(); private static final Map, Class> configs = new HashMap<>(); + public static final Set TRACE_HEADER_NAMES; static { @@ -350,7 +353,7 @@ public Transaction currentTransaction() { @Nullable @Override - public co.elastic.apm.agent.tracer.ErrorCapture getActiveError() { + public ErrorCapture getActiveError() { return ErrorCapture.getActive(); } @@ -973,4 +976,9 @@ public T require(Class type) { public Set getTraceHeaderNames() { return TRACE_HEADER_NAMES; } + + @Override + public ServiceInfo autoDetectedServiceInfo() { + return AutoDetectedServiceInfo.autoDetected(); + } } 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 febb3c6645..f645420cd9 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/Tracer.java @@ -18,7 +18,7 @@ */ package co.elastic.apm.agent.impl; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.impl.baggage.Baggage; import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.sampling.Sampler; @@ -30,7 +30,7 @@ import javax.annotation.Nullable; -public interface Tracer extends co.elastic.apm.agent.tracer.Tracer { +public interface Tracer extends co.elastic.apm.agent.tracer.service.ServiceAwareTracer, co.elastic.apm.agent.tracer.Tracer { @Nullable @Override diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java index 0bb8d6e634..c5ca3632c8 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java @@ -21,7 +21,7 @@ import co.elastic.apm.agent.bci.ElasticApmAgent; import co.elastic.apm.agent.common.util.SystemStandardOutputLogger; import co.elastic.apm.agent.configuration.CoreConfiguration; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.impl.AutoDetectedServiceInfo; import co.elastic.apm.agent.tracer.configuration.ByteValue; import co.elastic.apm.agent.report.ApmServerReporter; import org.apache.logging.log4j.Level; @@ -186,7 +186,7 @@ private LayoutComponentBuilder createLayout(ConfigurationBuilder awsLambdaEnvVariables = new HashMap<>(); awsLambdaEnvVariables.put("AWS_LAMBDA_FUNCTION_NAME", "my-lambda-function"); awsLambdaEnvVariables.put("AWS_LAMBDA_FUNCTION_VERSION", "24"); - ServiceInfo serviceInfo = callWithCustomEnvVariables(awsLambdaEnvVariables, () -> ServiceInfo.autoDetect(System.getProperties(), System.getenv())); + ServiceInfo serviceInfo = callWithCustomEnvVariables(awsLambdaEnvVariables, () -> AutoDetectedServiceInfo.autoDetect(System.getProperties(), System.getenv())); assertSoftly(softly -> { softly.assertThat(serviceInfo.getServiceName()).isEqualTo("my-lambda-function"); softly.assertThat(serviceInfo.getServiceVersion()).isEqualTo("24"); 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 baaf63d903..8d4a9f45db 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 @@ -21,7 +21,7 @@ import co.elastic.apm.agent.MockReporter; import co.elastic.apm.agent.common.util.WildcardMatcher; import co.elastic.apm.agent.configuration.CoreConfiguration; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.configuration.source.ConfigSources; import co.elastic.apm.agent.impl.baggage.Baggage; @@ -619,7 +619,7 @@ void testNotOverrideServiceNameWhenDefaultServiceNameConfigured() { CoreConfiguration coreConfig = localConfig.getConfig(CoreConfiguration.class); - assertThat(ServiceInfo.autoDetect(System.getProperties(), System.getenv())) + assertThat(AutoDetectedServiceInfo.autoDetect(System.getProperties(), System.getenv())) .isEqualTo(ServiceInfo.of(coreConfig.getServiceName())); assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isNull(); 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 6f646b0bc5..2ae1bd60f4 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporterTest.java @@ -19,7 +19,7 @@ package co.elastic.apm.agent.report.serialize; import co.elastic.apm.agent.MockReporter; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.ElasticApmTracerBuilder; 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 0f56c650da..7f696af5f1 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 @@ -19,7 +19,7 @@ package co.elastic.apm.agent.report.serialize; import co.elastic.apm.agent.configuration.MetricsConfiguration; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.metrics.Labels; import co.elastic.apm.agent.metrics.MetricCollector; import co.elastic.apm.agent.metrics.MetricRegistry; 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 6be447f2f4..7723217d1c 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,7 +18,8 @@ */ package co.elastic.apm.agent.pluginapi; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceAwareTracer; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.Tracer; import co.elastic.apm.agent.tracer.ErrorCapture; @@ -166,7 +167,7 @@ public SetServiceInfoForClassLoaderInstrumentation() { 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.require(Tracer.class).setServiceInfoForClassLoader(classLoader, ServiceInfo.of(serviceName, serviceVersion)); + tracer.require(ServiceAwareTracer.class).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 b74dfc1582..e6ce5c97a8 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,7 +18,8 @@ */ package co.elastic.apm.agent.pluginapi; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceAwareTracer; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.impl.Tracer; import co.elastic.apm.agent.impl.transaction.Id; import co.elastic.apm.agent.impl.transaction.TraceContext; @@ -176,7 +177,7 @@ public static class AdviceClass { 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.require(Tracer.class).getServiceInfoForClassLoader(classLoader); + ServiceInfo serviceInfo = tracer.require(ServiceAwareTracer.class).getServiceInfoForClassLoader(classLoader); if (serviceInfo != null) { ((Transaction) transaction).getTraceContext().setServiceInfo(serviceInfo.getServiceName(), serviceInfo.getServiceVersion()); } 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 cabb1fd282..50333280b9 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,7 +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.tracer.service.ServiceInfo; import co.elastic.apm.agent.impl.TracerInternalApiUtils; import co.elastic.apm.api.AbstractSpanImplAccessor; import co.elastic.apm.api.ElasticApm; 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 5b3b4af45f..b5a728eca6 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java @@ -19,7 +19,7 @@ package co.elastic.apm.api; import co.elastic.apm.AbstractApiTest; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.TracerInternalApiUtils; import co.elastic.apm.agent.impl.transaction.AbstractSpan; diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/EcsLoggingUtils.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/EcsLoggingUtils.java index 6ce83e7bab..bfae79d419 100644 --- a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/EcsLoggingUtils.java +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/EcsLoggingUtils.java @@ -18,8 +18,8 @@ */ package co.elastic.apm.agent.ecs_logging; -import co.elastic.apm.agent.configuration.ServiceInfo; -import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.tracer.service.ServiceAwareTracer; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; @@ -38,7 +38,7 @@ public class EcsLoggingUtils { private static final WeakSet versionChecked = WeakConcurrent.buildSet(); private static final WeakSet environmentChecked = WeakConcurrent.buildSet(); - private static final ElasticApmTracer tracer = GlobalTracer.get().require(ElasticApmTracer.class); + private static final ServiceAwareTracer tracer = GlobalTracer.get().require(ServiceAwareTracer.class); @Nullable public static String getServiceName() { 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 b507bb5aa9..e58bdf02dc 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 @@ -18,8 +18,8 @@ */ package co.elastic.apm.agent.servlet; -import co.elastic.apm.agent.configuration.ServiceInfo; -import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.tracer.service.ServiceAwareTracer; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.sdk.state.GlobalState; @@ -44,8 +44,8 @@ public static void determineServiceName(ServletContextAdapter void determineServiceName(ServletContextAdapter ServiceInfo detectServiceInfo(ServletContextAdapter adapter, ServletContext servletContext, ClassLoader servletContextClassLoader) { diff --git a/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/src/main/java/co/elastic/apm/agent/springwebmvc/AbstractSpringServiceNameInstrumentation.java b/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/src/main/java/co/elastic/apm/agent/springwebmvc/AbstractSpringServiceNameInstrumentation.java index 68a4d8f199..e38a5491f1 100644 --- a/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/src/main/java/co/elastic/apm/agent/springwebmvc/AbstractSpringServiceNameInstrumentation.java +++ b/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/src/main/java/co/elastic/apm/agent/springwebmvc/AbstractSpringServiceNameInstrumentation.java @@ -18,8 +18,8 @@ */ package co.elastic.apm.agent.springwebmvc; -import co.elastic.apm.agent.configuration.ServiceInfo; -import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.tracer.service.ServiceAwareTracer; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; import co.elastic.apm.agent.servlet.Constants; import co.elastic.apm.agent.servlet.ServletServiceNameHelper; @@ -76,14 +76,14 @@ public static class Helper { public static void detectSpringServiceName(ServletContextAdapter adapter, WebApplicationContext applicationContext, @Nullable ServletContext servletContext) { - ElasticApmTracer elasticApmTracer = tracer.probe(ElasticApmTracer.class); - if (elasticApmTracer == null) { + ServiceAwareTracer serviceAwareTracer = tracer.probe(ServiceAwareTracer.class); + if (serviceAwareTracer == null) { return; } // avoid having two service names for a standalone jar // one based on Implementation-Title and one based on spring.application.name - if (!ServiceInfo.autoDetected().isMultiServiceContainer()) { + if (!serviceAwareTracer.autoDetectedServiceInfo().isMultiServiceContainer()) { return; } @@ -105,7 +105,7 @@ public static void detectSpringServiceName(ServletContextAdapte ServiceInfo fromSpringApplicationNameProperty = ServiceInfo.of(applicationContext.getEnvironment().getProperty("spring.application.name", "")); ServiceInfo fromApplicationContextApplicationName = ServiceInfo.of(removeLeadingSlash(applicationContext.getApplicationName())); - elasticApmTracer.setServiceInfoForClassLoader(classLoader, fromSpringApplicationNameProperty + serviceAwareTracer.setServiceInfoForClassLoader(classLoader, fromSpringApplicationNameProperty .withFallback(fromServletContext) .withFallback(fromApplicationContextApplicationName)); } diff --git a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceAwareTracer.java b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceAwareTracer.java new file mode 100644 index 0000000000..30634926fe --- /dev/null +++ b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceAwareTracer.java @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.tracer.service; + +import co.elastic.apm.agent.tracer.Tracer; + +import javax.annotation.Nullable; + +public interface ServiceAwareTracer extends Tracer { + + @Nullable + ServiceInfo getServiceInfoForClassLoader(@Nullable ClassLoader initiatingClassLoader); + + void setServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo); + + ServiceInfo autoDetectedServiceInfo(); +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceInfo.java similarity index 53% rename from apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java rename to apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceInfo.java index b003a2256f..83d4b7eb79 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/ServiceInfo.java +++ b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/service/ServiceInfo.java @@ -16,34 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.configuration; - -import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; +package co.elastic.apm.agent.tracer.service; import javax.annotation.Nullable; -import java.util.Map; import java.util.Objects; -import java.util.Properties; import java.util.jar.Attributes; -import java.util.jar.JarFile; import java.util.jar.Manifest; public class ServiceInfo { - - private static final String JAR_VERSION_SUFFIX = "-(\\d+\\.)+(\\d+)(.*)?$"; private static final String DEFAULT_SERVICE_NAME = "unknown-java-service"; private static final ServiceInfo EMPTY = new ServiceInfo(null, null); - private static final ServiceInfo AUTO_DETECTED = autoDetect(System.getProperties(), PrivilegedActionUtils.getEnv()); private final String serviceName; @Nullable private final String serviceVersion; private final boolean multiServiceContainer; - public ServiceInfo(@Nullable String serviceName) { - this(serviceName, null); - } - private ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) { this(serviceName, serviceVersion, false); } @@ -82,76 +70,6 @@ private static String replaceDisallowedServiceNameChars(String serviceName) { return serviceName.replaceAll("[^a-zA-Z0-9 _-]", "-"); } - public static ServiceInfo autoDetected() { - return AUTO_DETECTED; - } - - public static ServiceInfo autoDetect(Properties sysProperties, Map sysEnv) { - String lambdaFunctionName = sysEnv.get("AWS_LAMBDA_FUNCTION_NAME"); - if (lambdaFunctionName != null) { - return new ServiceInfo(lambdaFunctionName, sysEnv.get("AWS_LAMBDA_FUNCTION_VERSION")); - } else { - ServiceInfo serviceInfo = createFromSunJavaCommand(sysProperties.getProperty("sun.java.command")); - if (serviceInfo != null) { - return serviceInfo; - } - return ServiceInfo.empty(); - } - } - - @Nullable - private static ServiceInfo createFromSunJavaCommand(@Nullable String command) { - if (command == null) { - return null; - } - command = command.trim(); - String serviceName = getContainerServiceName(command); - if (serviceName != null) { - return ServiceInfo.ofMultiServiceContainer(serviceName); - } - if (command.contains(".jar")) { - return fromJarCommand(command); - } else { - return fromMainClassCommand(command); - } - } - - @Nullable - private static String getContainerServiceName(String command) { - if (command.startsWith("org.apache.catalina.startup.Bootstrap")) { - return "tomcat-application"; - } else if (command.startsWith("org.eclipse.jetty")) { - return "jetty-application"; - } else if (command.startsWith("com.sun.enterprise.glassfish")) { - return "glassfish-application"; - } else if (command.contains("ws-server.jar")) { - return "websphere-application"; - } else if (command.contains("jboss-modules.jar")) { - return "jboss-application"; - } else if (command.contains("weblogic")) { - return "weblogic-application"; - } - return null; - } - - private static ServiceInfo fromJarCommand(String command) { - final String[] commandParts = command.split(" "); - ServiceInfo serviceInfoFromManifest = ServiceInfo.empty(); - ServiceInfo serviceInfoFromJarName = ServiceInfo.empty(); - for (String commandPart : commandParts) { - if (commandPart.endsWith(".jar")) { - try (JarFile jarFile = new JarFile(commandPart)) { - serviceInfoFromManifest = fromManifest(jarFile.getManifest()); - } catch (Exception ignored) { - } - - serviceInfoFromJarName = ServiceInfo.of(removeVersionFromJar(removePath(removeJarExtension(commandPart)))); - break; - } - } - return serviceInfoFromManifest.withFallback(serviceInfoFromJarName); - } - public static ServiceInfo fromManifest(@Nullable Manifest manifest) { if (manifest == null) { return ServiceInfo.empty(); @@ -162,29 +80,6 @@ public static ServiceInfo fromManifest(@Nullable Manifest manifest) { mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION)); } - private static String removeJarExtension(String commandPart) { - return commandPart.substring(0, commandPart.indexOf(".jar")); - } - - private static String removePath(String path) { - return path.substring(path.lastIndexOf("/") + 1).substring(path.lastIndexOf("\\") + 1); - } - - private static String removeVersionFromJar(String jarFileName) { - return jarFileName.replaceFirst(JAR_VERSION_SUFFIX, ""); - } - - private static ServiceInfo fromMainClassCommand(String command) { - final String mainClassName; - int indexOfSpace = command.indexOf(' '); - if (indexOfSpace != -1) { - mainClassName = command.substring(0, indexOfSpace); - } else { - mainClassName = command; - } - return new ServiceInfo(mainClassName.substring(mainClassName.lastIndexOf('.') + 1)); - } - public String getServiceName() { return serviceName; } 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 1886148281..d6c895d66e 100644 --- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java +++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/tests/ExternalPluginTestApp.java @@ -18,7 +18,7 @@ */ package co.elastic.apm.servlet.tests; -import co.elastic.apm.agent.configuration.ServiceInfo; +import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.impl.Tracer; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.servlet.AbstractServletContainerIntegrationTest; From 6c50ec57d4f0d8359f05aaeea5463fe3a0d681e1 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Fri, 8 Sep 2023 22:14:28 +0200 Subject: [PATCH 2/2] Restore package structure and split test for ServiceInfo. --- .../AutoDetectedServiceInfo.java | 23 +++- .../configuration/CoreConfiguration.java | 3 - .../apm/agent/impl/ElasticApmTracer.java | 3 +- .../logging/Log4j2ConfigurationFactory.java | 2 +- ....java => AutoDetectedServiceInfoTest.java} | 3 +- .../apm/agent/impl/ElasticApmTracerTest.java | 1 + .../agent/tracer/service/ServiceInfoTest.java | 125 ++++++++++++++++++ 7 files changed, 151 insertions(+), 9 deletions(-) rename apm-agent-core/src/main/java/co/elastic/apm/agent/{impl => configuration}/AutoDetectedServiceInfo.java (82%) rename apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/{ServiceInfoTest.java => AutoDetectedServiceInfoTest.java} (98%) create mode 100644 apm-agent-tracer/src/test/java/co/elastic/apm/agent/tracer/service/ServiceInfoTest.java diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfo.java similarity index 82% rename from apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java rename to apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfo.java index 006715a8e3..6129715193 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/AutoDetectedServiceInfo.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfo.java @@ -1,4 +1,22 @@ -package co.elastic.apm.agent.impl; +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.configuration; import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; import co.elastic.apm.agent.tracer.service.ServiceInfo; @@ -14,6 +32,9 @@ public class AutoDetectedServiceInfo { private static final ServiceInfo AUTO_DETECTED = AutoDetectedServiceInfo.autoDetect(System.getProperties(), PrivilegedActionUtils.getEnv()); + private AutoDetectedServiceInfo() { + } + public static ServiceInfo autoDetected() { return AUTO_DETECTED; } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index 3decdeefc5..7200f1ab4f 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -20,8 +20,6 @@ import co.elastic.apm.agent.bci.ElasticApmAgent; import co.elastic.apm.agent.common.util.WildcardMatcher; -import co.elastic.apm.agent.impl.AutoDetectedServiceInfo; -import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.matcher.MethodMatcher; import co.elastic.apm.agent.matcher.MethodMatcherValueConverter; @@ -33,7 +31,6 @@ import co.elastic.apm.agent.tracer.configuration.TimeDuration; import co.elastic.apm.agent.tracer.configuration.TimeDurationValueConverter; import co.elastic.apm.agent.tracer.configuration.WildcardMatcherValueConverter; -import co.elastic.apm.agent.tracer.service.ServiceInfo; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.ConfigurationOptionProvider; import org.stagemonitor.configuration.converter.AbstractValueConverter; 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 841b40b450..586511857e 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 @@ -22,6 +22,7 @@ import co.elastic.apm.agent.collections.WeakReferenceCountedMap; import co.elastic.apm.agent.common.JvmRuntimeInfo; import co.elastic.apm.agent.common.util.WildcardMatcher; +import co.elastic.apm.agent.configuration.AutoDetectedServiceInfo; import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.configuration.MetricsConfiguration; import co.elastic.apm.agent.configuration.ServerlessConfiguration; @@ -74,13 +75,11 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; -import java.util.jar.JarFile; /** diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java index c5ca3632c8..e5da6fe806 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/logging/Log4j2ConfigurationFactory.java @@ -21,7 +21,7 @@ import co.elastic.apm.agent.bci.ElasticApmAgent; import co.elastic.apm.agent.common.util.SystemStandardOutputLogger; import co.elastic.apm.agent.configuration.CoreConfiguration; -import co.elastic.apm.agent.impl.AutoDetectedServiceInfo; +import co.elastic.apm.agent.configuration.AutoDetectedServiceInfo; import co.elastic.apm.agent.tracer.configuration.ByteValue; import co.elastic.apm.agent.report.ApmServerReporter; import org.apache.logging.log4j.Level; diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfoTest.java similarity index 98% rename from apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java rename to apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfoTest.java index aa58bffab3..e900e81256 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/ServiceInfoTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfoTest.java @@ -18,7 +18,6 @@ */ package co.elastic.apm.agent.configuration; -import co.elastic.apm.agent.impl.AutoDetectedServiceInfo; import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.util.CustomEnvVariables; import org.junit.jupiter.api.Test; @@ -33,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; -class ServiceInfoTest extends CustomEnvVariables { +class AutoDetectedServiceInfoTest extends CustomEnvVariables { private static String getDefaultServiceName(@Nullable String sunJavaCommand) { Properties properties = new Properties(); 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 8d4a9f45db..5283795cf5 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 @@ -20,6 +20,7 @@ import co.elastic.apm.agent.MockReporter; import co.elastic.apm.agent.common.util.WildcardMatcher; +import co.elastic.apm.agent.configuration.AutoDetectedServiceInfo; import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.tracer.service.ServiceInfo; import co.elastic.apm.agent.configuration.SpyConfiguration; diff --git a/apm-agent-tracer/src/test/java/co/elastic/apm/agent/tracer/service/ServiceInfoTest.java b/apm-agent-tracer/src/test/java/co/elastic/apm/agent/tracer/service/ServiceInfoTest.java new file mode 100644 index 0000000000..68a3323321 --- /dev/null +++ b/apm-agent-tracer/src/test/java/co/elastic/apm/agent/tracer/service/ServiceInfoTest.java @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.tracer.service; + +import org.junit.jupiter.api.Test; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import static org.assertj.core.api.Assertions.assertThat; + +class ServiceInfoTest { + + @Test + void testNormalizedName() { + checkServiceInfoEmpty(ServiceInfo.of("")); + checkServiceInfoEmpty(ServiceInfo.of(" ")); + + assertThat(ServiceInfo.of(" a")).isEqualTo(ServiceInfo.of("a")); + assertThat(ServiceInfo.of(" !web# ")).isEqualTo(ServiceInfo.of("-web-")); + } + + @Test + void createEmpty() { + checkServiceInfoEmpty(ServiceInfo.empty()); + assertThat(ServiceInfo.empty()) + .isEqualTo(ServiceInfo.empty()); + + } + + @Test + void of() { + checkServiceInfoEmpty(ServiceInfo.of(null)); + checkServiceInfoEmpty(ServiceInfo.of(null, null)); + + checkServiceInfo(ServiceInfo.of("service"), "service", null); + checkServiceInfo(ServiceInfo.of("service", null), "service", null); + checkServiceInfo(ServiceInfo.of("service", "1.2.3"), "service", "1.2.3"); + + } + + @Test + void checkEquality() { + checkEquality(ServiceInfo.of(null), ServiceInfo.empty()); + checkEquality(ServiceInfo.of(""), ServiceInfo.empty()); + checkEquality(ServiceInfo.of(null, null), ServiceInfo.empty()); + checkEquality(ServiceInfo.of("", ""), ServiceInfo.empty()); + } + + private static void checkEquality(ServiceInfo first, ServiceInfo second){ + assertThat(first) + .isEqualTo(second); + + assertThat(first.hashCode()) + .isEqualTo(second.hashCode()); + } + + @Test + void fromManifest() { + checkServiceInfoEmpty(ServiceInfo.fromManifest(null)); + checkServiceInfoEmpty(ServiceInfo.fromManifest(null)); + checkServiceInfoEmpty(ServiceInfo.fromManifest(new Manifest())); + + ServiceInfo serviceInfo = ServiceInfo.fromManifest(manifest(Map.of( + Attributes.Name.IMPLEMENTATION_TITLE.toString(), "service-name" + ))); + checkServiceInfo(serviceInfo, "service-name", null); + + serviceInfo = ServiceInfo.fromManifest(manifest(Map.of( + Attributes.Name.IMPLEMENTATION_TITLE.toString(), "my-service", + Attributes.Name.IMPLEMENTATION_VERSION.toString(), "v42" + ))); + checkServiceInfo(serviceInfo, "my-service", "v42"); + } + + private static Manifest manifest(Map entries) { + Manifest manifest = new Manifest(); + + Attributes attributes = manifest.getMainAttributes(); + entries.forEach(attributes::putValue); + + return manifest; + } + + private static void checkServiceInfoEmpty(ServiceInfo serviceInfo) { + assertThat(serviceInfo.isEmpty()).isTrue(); + assertThat(serviceInfo.getServiceName()).isEqualTo("unknown-java-service"); + assertThat(serviceInfo.hasServiceName()).isFalse(); + assertThat(serviceInfo.getServiceVersion()).isNull(); + + assertThat(serviceInfo).isEqualTo(ServiceInfo.empty()); + } + + private static void checkServiceInfo(ServiceInfo serviceInfo, String expectedServiceName, @Nullable String expectedServiceVersion) { + assertThat(serviceInfo.isEmpty()).isFalse(); + assertThat(serviceInfo.getServiceName()).isEqualTo(expectedServiceName); + assertThat(serviceInfo.hasServiceName()).isTrue(); + if (expectedServiceVersion == null) { + assertThat(serviceInfo.getServiceVersion()).isNull(); + } else { + assertThat(serviceInfo.getServiceVersion()).isEqualTo(expectedServiceVersion); + } + + assertThat(serviceInfo).isEqualTo(ServiceInfo.of(expectedServiceName, expectedServiceVersion)); + } + +}