diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfo.java new file mode 100644 index 0000000000..6129715193 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AutoDetectedServiceInfo.java @@ -0,0 +1,130 @@ +/* + * 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; + +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()); + + private AutoDetectedServiceInfo() { + } + + 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/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index fafde04a92..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 @@ -163,7 +163,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 +203,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/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index 1b7729ad75..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,10 +22,11 @@ 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; -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; @@ -93,6 +94,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 +352,7 @@ public Transaction currentTransaction() { @Nullable @Override - public co.elastic.apm.agent.tracer.ErrorCapture getActiveError() { + public ErrorCapture getActiveError() { return ErrorCapture.getActive(); } @@ -973,4 +975,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..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.configuration.ServiceInfo; +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; @@ -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..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,8 +20,9 @@ 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.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 +620,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/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)); + } + +} 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;