co.elastic.apm.attach.ElasticApmAttacher Maven / Gradle / Ivy
 The newest version!
        
        /*
 * 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.attach;
import co.elastic.apm.agent.common.util.ResourceExtractionUtil;
import co.elastic.apm.agent.common.util.SystemStandardOutputLogger;
import co.elastic.apm.attach.bytebuddy.agent.ByteBuddyAgent;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
 * Attaches the Elastic Apm agent to the current or a remote JVM
 */
public class ElasticApmAttacher {
    /**
     * This key is very short on purpose.
     * The longer the agent argument ({@code -javaagent:=}), the greater the chance that the max length of the agent argument is reached.
     * Because of a bug in the {@linkplain ByteBuddyAgent.AttachmentProvider.ForEmulatedAttachment emulated attachment},
     * this can even lead to segfaults.
     */
    private static final String TEMP_PROPERTIES_FILE_KEY = "c";
    /**
     * Attaches the Elastic Apm agent to the current JVM.
     * 
     * This method may only be invoked once.
     * 
     * 
     * Tries to load {@code elasticapm.properties} from the classpath, if exists.
     * 
     *
     * @throws IllegalStateException if there was a problem while attaching the agent to this VM
     */
    public static void attach() {
        attach(loadPropertiesFromClasspath("elasticapm.properties"));
    }
    /**
     * Attaches the Elastic Apm agent to the current JVM.
     * 
     * This method may only be invoked once.
     * 
     *
     * @param propertiesLocation the location within the classpath which contains the agent configuration properties file
     * @throws IllegalStateException if there was a problem while attaching the agent to this VM
     * @since 1.11.0
     */
    public static void attach(String propertiesLocation) {
        attach(loadPropertiesFromClasspath(propertiesLocation));
    }
    private static Map loadPropertiesFromClasspath(String propertiesLocation) {
        Map propertyMap = new HashMap<>();
        final Properties props = new Properties();
        try (InputStream resourceStream = ElasticApmAttacher.class.getClassLoader().getResourceAsStream(propertiesLocation)) {
            if (resourceStream != null) {
                props.load(resourceStream);
                for (String propertyName : props.stringPropertyNames()) {
                    propertyMap.put(propertyName, props.getProperty(propertyName));
                }
            }
        } catch (IOException e) {
            SystemStandardOutputLogger.printStackTrace(e);
        }
        return propertyMap;
    }
    /**
     * Attaches the Elastic Apm agent to the current JVM.
     * 
     * This method may only be invoked once.
     * 
     *
     * @param configuration the agent configuration
     * @throws IllegalStateException if there was a problem while attaching the agent to this VM
     */
    public static void attach(Map configuration) {
        // optimization, this is checked in AgentMain#init again
        if (Boolean.getBoolean("ElasticApm.attached")) {
            return;
        }
        attach(ByteBuddyAgent.ProcessProvider.ForCurrentVm.INSTANCE.resolve(), configuration);
    }
    /**
     * Store configuration to a temporary file
     *
     * @param configuration agent configuration
     * @param folder        temporary folder, use {@literal null} to use default
     * @return created file if any, {@literal null} if none was created
     */
    static File createTempProperties(Map configuration, @Nullable File folder) {
        File tempFile = null;
        if (!configuration.isEmpty()) {
            Properties properties = new Properties();
            properties.putAll(configuration);
            try {
                tempFile = File.createTempFile("elstcapm", ".tmp", folder);
                try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
                    properties.store(outputStream, null);
                }
            } catch (IOException e) {
                SystemStandardOutputLogger.printStackTrace(e);
            }
        }
        return tempFile;
    }
    /**
     * Attaches the agent to a remote JVM
     *
     * @param pid           the PID of the JVM the agent should be attached on
     * @param configuration the agent configuration
     */
    public static void attach(String pid, Map configuration) {
        attach(pid, configuration, AgentJarFileHolder.INSTANCE.agentJarFile);
    }
    /**
     * Attaches the agent to a remote JVM
     *
     * @param pid           the PID of the JVM the agent should be attached on
     * @param configuration the agent configuration
     * @param agentJarFile  the agent jar file
     */
    public static void attach(String pid, Map configuration, File agentJarFile) {
        // making a copy of provided configuration as user might have used an immutable map impl.
        // and guard against user-provided null keys and values
        Map config = new HashMap<>();
        for (Map.Entry entry : configuration.entrySet()) {
            if (entry.getKey() != null && entry.getValue() != null) {
                config.put(entry.getKey(), entry.getValue());
            }
        }
        if (!config.containsKey("activation_method")) {
            config.put("activation_method", "PROGRAMMATIC_SELF_ATTACH");
        }
        File tempFile = createTempProperties(config, null);
        String agentArgs = tempFile == null ? null : TEMP_PROPERTIES_FILE_KEY + "=" + tempFile.getAbsolutePath();
        attachWithFallback(agentJarFile, pid, agentArgs);
        if (tempFile != null) {
            if (!tempFile.delete()) {
                tempFile.deleteOnExit();
            }
        }
    }
    private static void attachWithFallback(File agentJarFile, String pid, String agentArgs) {
        try {
            // while the native providers may report to be supported and appear to work properly, in practice there are
            // cases (Docker without '--init' option on some JDK images like 'openjdk:8-jdk-alpine') where the accessor
            // returned by the provider will not work as expected at attachment time.
            ByteBuddyAgent.attach(agentJarFile, pid, agentArgs, ElasticAttachmentProvider.get());
        } catch (RuntimeException e1) {
            try {
                ByteBuddyAgent.attach(agentJarFile, pid, agentArgs, ElasticAttachmentProvider.getFallback());
            } catch (RuntimeException e2) {
                // output the two exceptions for debugging
                SystemStandardOutputLogger.stdErrInfo("Unable to attach with fallback provider:");
                SystemStandardOutputLogger.printStackTrace(e2);
                SystemStandardOutputLogger.stdErrInfo("Unable to attach with regular provider:");
                SystemStandardOutputLogger.printStackTrace(e1);
            }
        }
    }
    /**
     * Attaches the agent to a remote JVM
     *
     * @param pid       the PID of the JVM the agent should be attached on
     * @param agentArgs the agent arguments
     * @deprecated use {@link #attach(String, Map)}
     */
    @Deprecated
    public static void attach(String pid, String agentArgs) {
        attachWithFallback(AgentJarFileHolder.INSTANCE.agentJarFile, pid, agentArgs);
    }
    public static File getBundledAgentJarFile() {
        return AgentJarFileHolder.INSTANCE.agentJarFile;
    }
    private enum AgentJarFileHolder {
        INSTANCE;
        // initializes lazily and ensures its only loaded once
        final File agentJarFile = getAgentJarFile();
        private static File getAgentJarFile() {
            if (ElasticApmAttacher.class.getResource("/elastic-apm-agent.jar") != null) {
                // packaged agent as resource
                return ResourceExtractionUtil.extractResourceToTempDirectory("elastic-apm-agent.jar", "elastic-apm-agent", ".jar").toFile();
            }
            // Running attacher without proper packaging is quite common when running it from the IDE without having
            // it packaged from CLI beforehand
            throw new IllegalStateException("unable to get packaged agent within attacher jar.");
        }
    }
}
              © 2015 - 2025 Weber Informatics LLC | Privacy Policy