Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ endif::[]
[[release-notes-1.28.5]]
==== 1.28.5 - YYYY/MM/DD

[float]
===== Breaking changes
* Changes in service name auto-discovery of jar files (see Features section)

[float]
===== Features
* Exceptions that are logged using the fatal log level are now captured (log4j2 only) - {pull}2377[#2377]
* Replaced `authorization` in the default value of `sanitize_field_names` with `*auth*` - {pull}2326[#2326]
* Unsampled transactions are dropped and not sent to the APM-Server if the APM-Server version is 8.0+ - {pull}2329[#2329]
* Adding agent logging capabilities to our SDK, making it available for external plugins - {pull}2390[#2390]
* When the `MANIFEST.MF` of the main jar contains the `Implementation-Title` attribute, it is used as the default service name - {pull}1921[#1921]
Note: this may change your service names if you relied on the auto-discovery that uses the name of the jar file. If that jar file also contains an `Implementation-Title` attribute in the `MANIFEST.MF` file, the latter will take precedence.
* When the `MANIFEST.MF` of the main jar contains the `Implementation-Version` attribute, it is used as the default service version (except for application servers) - {pull}1922[#1922]
* Added support for overwritting the service version per classloader - {pull}1726[#1726]
* Service name auto-discovery improvements
** For applications deployed to application servers (`war` files) and standalone jars that are started with `java -jar`,
the agent now discovers the `META-INF/MANIFEST.MF` file.
** If the manifest contains the `Implementation-Title` attribute, it is used as the default service name - {pull}1921[#1921], {pull}2434[#2434] +
*Note*: this may change your service names if you relied on the auto-discovery that uses the name of the jar file.
If that jar file also contains an `Implementation-Title` attribute in the `MANIFEST.MF` file, the latter will take precedence.
** When the manifest contains the `Implementation-Version` attribute, it is used as the default service version - {pull}1726[#1726], {pull}1922[#1922], {pull}2434[#2434]
* Added support for instrumenting Struts 2 static resource requests - {pull}1949[#1949]
* Added support for Java/Jakarta WebSocket ServerEndpoint - {pull}2281[#2281]
* Added support for setting the service name on Log4j2's EcsLayout - {pull}2296[#2296]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
package co.elastic.apm.agent.configuration;

import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

public class ServiceInfo {

private static final String JAR_VERSION_SUFFIX = "-(\\d+\\.)+(\\d+)(.*)?$";
private static final String DEFAULT_SERVICE_NAME = "unknown-java-service";
private static final ServiceInfo EMPTY = new ServiceInfo(null, null);
private static final ServiceInfo AUTO_DETECTED = autoDetect(System.getProperties());

private final String serviceName;
Expand All @@ -37,7 +40,7 @@ public ServiceInfo(@Nullable String serviceName) {
this(serviceName, null);
}

public ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) {
private ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion) {
if (serviceName == null || serviceName.trim().isEmpty()) {
this.serviceName = DEFAULT_SERVICE_NAME;
} else {
Expand All @@ -46,16 +49,23 @@ public ServiceInfo(@Nullable String serviceName, @Nullable String serviceVersion
this.serviceVersion = serviceVersion;
}

public String getServiceName() {
return serviceName;
public static ServiceInfo empty() {
return EMPTY;
}

@Nullable
public String getServiceVersion() {
return serviceVersion;
public static ServiceInfo of(@Nullable String serviceName) {
return of(serviceName, null);
}

public static ServiceInfo of(@Nullable String serviceName, @Nullable String serviceVersion) {
if ((serviceName == null || serviceName.isEmpty()) &&
(serviceVersion == null || serviceVersion.isEmpty())) {
return empty();
}
return new ServiceInfo(serviceName, serviceVersion);
}

public static String replaceDisallowedServiceNameChars(String serviceName) {
private static String replaceDisallowedServiceNameChars(String serviceName) {
return serviceName.replaceAll("[^a-zA-Z0-9 _-]", "-");
}

Expand All @@ -72,7 +82,7 @@ public static ServiceInfo autoDetect(Properties properties) {
if (serviceInfo != null) {
return serviceInfo;
}
return new ServiceInfo(null);
return ServiceInfo.empty();
}
}

Expand All @@ -84,12 +94,12 @@ private static ServiceInfo createFromSunJavaCommand(@Nullable String command) {
command = command.trim();
String serviceName = getContainerServiceName(command);
if (serviceName != null) {
return new ServiceInfo(serviceName);
return ServiceInfo.of(serviceName);
}
if (command.contains(".jar")) {
return parseJarCommand(command);
return fromJarCommand(command);
} else {
return parseMainClass(command);
return fromMainClassCommand(command);
}
}

Expand All @@ -111,26 +121,32 @@ private static String getContainerServiceName(String command) {
return null;
}

private static ServiceInfo parseJarCommand(String command) {
private static ServiceInfo fromJarCommand(String command) {
final String[] commandParts = command.split(" ");
String serviceName = null;
String serviceVersion = null;
ServiceInfo serviceInfoFromManifest = ServiceInfo.empty();
ServiceInfo serviceInfoFromJarName = ServiceInfo.empty();
for (String commandPart : commandParts) {
if (commandPart.endsWith(".jar")) {
try (JarFile jarFile = new JarFile(commandPart)) {
Attributes mainAttributes = jarFile.getManifest().getMainAttributes();
serviceName = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
serviceVersion = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
serviceInfoFromManifest = fromManifest(jarFile.getManifest());
} catch (Exception ignored) {
}

if (serviceName == null || serviceName.isEmpty()) {
serviceName = removeVersionFromJar(removePath(removeJarExtension(commandPart)));
}
serviceInfoFromJarName = ServiceInfo.of(removeVersionFromJar(removePath(removeJarExtension(commandPart))));
break;
}
}
return new ServiceInfo(serviceName, serviceVersion);
return serviceInfoFromManifest.withFallback(serviceInfoFromJarName);
}

public static ServiceInfo fromManifest(@Nullable Manifest manifest) {
if (manifest == null) {
return ServiceInfo.empty();
}
Attributes mainAttributes = manifest.getMainAttributes();
return ServiceInfo.of(
mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE),
mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION));
}

private static String removeJarExtension(String commandPart) {
Expand All @@ -145,7 +161,7 @@ private static String removeVersionFromJar(String jarFileName) {
return jarFileName.replaceFirst(JAR_VERSION_SUFFIX, "");
}

private static ServiceInfo parseMainClass(String command) {
private static ServiceInfo fromMainClassCommand(String command) {
final String mainClassName;
int indexOfSpace = command.indexOf(' ');
if (indexOfSpace != -1) {
Expand All @@ -155,4 +171,48 @@ private static ServiceInfo parseMainClass(String command) {
}
return new ServiceInfo(mainClassName.substring(mainClassName.lastIndexOf('.') + 1));
}

public String getServiceName() {
return serviceName;
}

@Nullable
public String getServiceVersion() {
return serviceVersion;
}

public ServiceInfo withFallback(ServiceInfo fallback) {
return ServiceInfo.of(
hasServiceName() ? serviceName : fallback.serviceName,
serviceVersion != null ? serviceVersion : fallback.serviceVersion);
}

public boolean hasServiceName() {
return !serviceName.equals(DEFAULT_SERVICE_NAME);
}

public boolean isEmpty() {
return !hasServiceName() && serviceVersion == null;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceInfo that = (ServiceInfo) o;
return serviceName.equals(that.serviceName) && Objects.equals(serviceVersion, that.serviceVersion);
}

@Override
public int hashCode() {
return Objects.hash(serviceName, serviceVersion);
}

@Override
public String toString() {
return "ServiceInfo{" +
"name='" + serviceName + '\'' +
", version='" + serviceVersion + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@
import co.elastic.apm.agent.report.ApmServerClient;
import co.elastic.apm.agent.report.Reporter;
import co.elastic.apm.agent.report.ReporterConfiguration;
import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent;
import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap;
import co.elastic.apm.agent.util.DependencyInjectingServiceLoader;
import co.elastic.apm.agent.util.ExecutorUtils;
import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import org.stagemonitor.configuration.ConfigurationOption;
import org.stagemonitor.configuration.ConfigurationOptionProvider;
import org.stagemonitor.configuration.ConfigurationRegistry;
Expand Down Expand Up @@ -748,25 +748,19 @@ public List<ServiceInfo> getServiceInfoOverrides() {
}

@Override
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
overrideServiceInfoForClassLoader(classLoader, serviceName, null);
}

@Override
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) {
// overriding the service name/version for the bootstrap class loader is not an actual use-case
// null may also mean we don't know about the initiating class loader
if (classLoader == null
|| serviceName == null || serviceName.isEmpty()
|| !serviceInfo.hasServiceName()
// if the service name is set explicitly, don't override it
|| coreConfiguration.getServiceNameConfig().getUsedKey() != null) {
return;
}

ServiceInfo serviceInfo = new ServiceInfo(serviceName, serviceVersion);
logger.debug("Using `{}` as the service name and `{}` as the service version for class loader [{}]", serviceInfo.getServiceName(), serviceInfo.getServiceVersion(), classLoader);
if (!serviceInfoByClassLoader.containsKey(classLoader)) {
serviceInfoByClassLoader.putIfAbsent(classLoader, new ServiceInfo(serviceName, serviceVersion));
serviceInfoByClassLoader.putIfAbsent(classLoader, serviceInfo);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package co.elastic.apm.agent.impl;

import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.impl.error.ErrorCapture;
import co.elastic.apm.agent.impl.sampling.Sampler;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
Expand Down Expand Up @@ -204,13 +205,8 @@ public TracerState getState() {
}

@Override
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
tracer.overrideServiceInfoForClassLoader(classLoader, serviceName);
}

@Override
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
tracer.overrideServiceInfoForClassLoader(classLoader, serviceName, serviceVersion);
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) {
tracer.overrideServiceInfoForClassLoader(classLoader, serviceInfo);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package co.elastic.apm.agent.impl;

import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.impl.error.ErrorCapture;
import co.elastic.apm.agent.impl.sampling.Sampler;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
Expand Down Expand Up @@ -130,11 +131,7 @@ public TracerState getState() {
}

@Override
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
}

@Override
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
public void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo) {
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package co.elastic.apm.agent.impl;

import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.impl.error.ErrorCapture;
import co.elastic.apm.agent.impl.sampling.Sampler;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
Expand Down Expand Up @@ -152,18 +153,6 @@ <C> Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet

TracerState getState();

/**
* Overrides the service name for all {@link Transaction}s,
* {@link Span}s and {@link ErrorCapture}s which are created by the service which corresponds to the provided {@link ClassLoader}.
* <p>
* The main use case is being able to differentiate between multiple services deployed to the same application server.
* </p>
*
* @param classLoader the class loader which corresponds to a particular service
* @param serviceName the service name for this class loader
*/
void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName);

/**
* Overrides the service name and version for all {@link Transaction}s,
* {@link Span}s and {@link ErrorCapture}s which are created by the service which corresponds to the provided {@link ClassLoader}.
Expand All @@ -172,10 +161,9 @@ <C> Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGet
* </p>
*
* @param classLoader the class loader which corresponds to a particular service
* @param serviceName the service name for this class loader
* @param serviceVersion the service version for this class loader
* @param serviceInfo the service name and version for this class loader
*/
void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion);
void overrideServiceInfoForClassLoader(@Nullable ClassLoader classLoader, ServiceInfo serviceInfo);

/**
* Called when the container shuts down.
Expand Down
Loading