diff --git a/runtime-attach-core/README.md b/runtime-attach-core/README.md new file mode 100644 index 00000000..3cc20b27 --- /dev/null +++ b/runtime-attach-core/README.md @@ -0,0 +1,5 @@ +# Runtime attachment core + +This project is dedicated to providers of the [OpenTelemetry Java agent](https://github.com/open-telemetry/opentelemetry-java-instrumentation). It helps them to build a dependency to runtime attach their agent jar file. + +You can find [here](../runtime-attach/README.md) the project of the distribution published by OpenTelemetry. diff --git a/runtime-attach-core/build.gradle.kts b/runtime-attach-core/build.gradle.kts new file mode 100644 index 00000000..d2593043 --- /dev/null +++ b/runtime-attach-core/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") +} + +description = "To help in create an OpenTelemetry distro able to runtime attach an OpenTelemetry Java Instrumentation agent" + +dependencies { + implementation("net.bytebuddy:byte-buddy-agent:1.11.18") + + // Used by byte-buddy but not brought in as a transitive dependency. + compileOnly("com.google.code.findbugs:annotations") +} diff --git a/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java b/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java similarity index 51% rename from runtime-attach/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java rename to runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java index a6af835e..9a08b50f 100644 --- a/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java +++ b/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java @@ -5,24 +5,25 @@ package io.opentelemetry.contrib.attach; -import io.opentelemetry.javaagent.OpenTelemetryAgent; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.security.CodeSource; final class AgentFileProvider { - static File getAgentFile() { + private final String agentJarResourceName; - verifyExistenceOfAgentJarFile(); + AgentFileProvider(String agentJarResourceName) { + this.agentJarResourceName = agentJarResourceName; + } + + File getAgentFile() { Path tempDirPath = createTempDir(); - Path tempAgentJarPath = createTempAgentJarFile(tempDirPath); + Path tempAgentJarPath = createTempAgentJarFileIn(tempDirPath); deleteTempDirOnJvmExit(tempDirPath, tempAgentJarPath); @@ -34,13 +35,6 @@ final class AgentFileProvider { tempDirPath.toFile().deleteOnExit(); } - private static void verifyExistenceOfAgentJarFile() { - CodeSource codeSource = OpenTelemetryAgent.class.getProtectionDomain().getCodeSource(); - if (codeSource == null) { - throw new IllegalStateException("could not get agent jar location"); - } - } - private static Path createTempDir() { Path tempDir; try { @@ -51,23 +45,18 @@ final class AgentFileProvider { return tempDir; } - private static Path createTempAgentJarFile(Path tempDir) { - URL url = OpenTelemetryAgent.class.getProtectionDomain().getCodeSource().getLocation(); - try { - return copyTo(url, tempDir, "agent.jar"); + private Path createTempAgentJarFileIn(Path tempDir) { + Path agentJarPath = tempDir.resolve("agent.jar"); + try (InputStream jarAsInputStream = + AgentFileProvider.class.getResourceAsStream(this.agentJarResourceName)) { + if (jarAsInputStream == null) { + throw new IllegalStateException(this.agentJarResourceName + " resource can't be found"); + } + Files.copy(jarAsInputStream, agentJarPath); } catch (IOException e) { throw new IllegalStateException( "Runtime attachment can't create agent jar file in temp directory", e); } + return agentJarPath; } - - private static Path copyTo(URL url, Path tempDir, String fileName) throws IOException { - Path tempFile = tempDir.resolve(fileName); - try (InputStream in = url.openStream()) { - Files.copy(in, tempFile); - } - return tempFile; - } - - private AgentFileProvider() {} } diff --git a/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/CoreRuntimeAttach.java b/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/CoreRuntimeAttach.java new file mode 100644 index 00000000..d220e52d --- /dev/null +++ b/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/CoreRuntimeAttach.java @@ -0,0 +1,124 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.attach; + +import java.io.File; +import java.lang.management.ManagementFactory; +import net.bytebuddy.agent.ByteBuddyAgent; + +/** This class allows you to attach the OpenTelemetry Java agent at runtime. */ +public final class CoreRuntimeAttach { + + private static final String AGENT_ENABLED_PROPERTY = "otel.javaagent.enabled"; + private static final String AGENT_ENABLED_ENV_VAR = "OTEL_JAVAAGENT_ENABLED"; + static final String MAIN_METHOD_CHECK_PROP = + "otel.javaagent.testing.runtime-attach.main-method-check"; + + private final String agentJarResourceName; + + /** + * Creates a new {@code DistroRuntimeAttach} from the resource name of the agent jar. + * + * @param agentJarResourceName Resource name of the agent jar. + */ + public CoreRuntimeAttach(String agentJarResourceName) { + this.agentJarResourceName = agentJarResourceName; + } + + /** + * Attach the OpenTelemetry Java agent to the current JVM. The attachment must be requested at the + * beginning of the main method. + */ + public void attachJavaagentToCurrentJVM() { + if (!shouldAttach()) { + return; + } + + AgentFileProvider agentFileProvider = new AgentFileProvider(agentJarResourceName); + + File javaagentFile = agentFileProvider.getAgentFile(); + ByteBuddyAgent.attach(javaagentFile, getPid()); + + if (!agentIsAttached()) { + printError("Agent was not attached. An unexpected issue has happened."); + } + } + + @SuppressWarnings("SystemOut") + private static void printError(String message) { + // not using java.util.logging in order to avoid initializing the global LogManager + // too early (and incompatibly with the user's app), + // and because this is too early to use the Javaagent's PatchLogger + System.err.println(message); + } + + private static boolean shouldAttach() { + if (agentIsDisabledWithProp()) { + return false; + } + if (agentIsDisabledWithEnvVar()) { + return false; + } + if (agentIsAttached()) { + return false; + } + if (mainMethodCheckIsEnabled() && !isMainThread()) { + printError( + "Agent is not attached because runtime attachment was not requested from main thread."); + return false; + } + if (mainMethodCheckIsEnabled() && !isMainMethod()) { + printError( + "Agent is not attached because runtime attachment was not requested from main method."); + return false; + } + return true; + } + + private static boolean agentIsDisabledWithProp() { + String agentEnabledPropValue = System.getProperty(AGENT_ENABLED_PROPERTY); + return "false".equalsIgnoreCase(agentEnabledPropValue); + } + + private static boolean agentIsDisabledWithEnvVar() { + String agentEnabledEnvVarValue = System.getenv(AGENT_ENABLED_ENV_VAR); + return "false".equals(agentEnabledEnvVarValue); + } + + private static boolean agentIsAttached() { + try { + Class.forName("io.opentelemetry.javaagent.OpenTelemetryAgent", false, null); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private static boolean mainMethodCheckIsEnabled() { + String mainThreadCheck = System.getProperty(MAIN_METHOD_CHECK_PROP); + return !"false".equals(mainThreadCheck); + } + + private static boolean isMainThread() { + Thread currentThread = Thread.currentThread(); + return "main".equals(currentThread.getName()); + } + + static boolean isMainMethod() { + StackTraceElement bottomOfStack = findBottomOfStack(Thread.currentThread()); + String methodName = bottomOfStack.getMethodName(); + return "main".equals(methodName); + } + + private static StackTraceElement findBottomOfStack(Thread thread) { + StackTraceElement[] stackTrace = thread.getStackTrace(); + return stackTrace[stackTrace.length - 1]; + } + + private static String getPid() { + return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + } +} diff --git a/runtime-attach/build.gradle.kts b/runtime-attach/build.gradle.kts index 52aa58b7..3d883d9a 100644 --- a/runtime-attach/build.gradle.kts +++ b/runtime-attach/build.gradle.kts @@ -3,11 +3,13 @@ plugins { id("otel.publish-conventions") } -description = "Utility to attach OpenTelemetry Java Instrumentation agent from classpath" +description = "To runtime attach the OpenTelemetry Java Instrumentation agent" + +val agent: Configuration by configurations.creating dependencies { - compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent:1.6.0") - implementation("net.bytebuddy:byte-buddy-agent:1.11.18") + implementation(project(":runtime-attach-core")) + agent("io.opentelemetry.javaagent:opentelemetry-javaagent:1.15.0") // Used by byte-buddy but not brought in as a transitive dependency. compileOnly("com.google.code.findbugs:annotations") @@ -23,3 +25,13 @@ tasks.test { useJUnitPlatform() setForkEvery(1) // One JVM by test class to avoid a test class launching a runtime attachment influences the behavior of another test class } + +tasks { + jar { + inputs.files(agent) + from({ + agent.singleFile + }) + rename { "otel-agent.jar" } + } +} diff --git a/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java b/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java index 741d23e8..7495b727 100644 --- a/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java +++ b/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java @@ -5,108 +5,18 @@ package io.opentelemetry.contrib.attach; -import java.io.File; -import java.lang.management.ManagementFactory; -import net.bytebuddy.agent.ByteBuddyAgent; - /** This class allows you to attach the OpenTelemetry Java agent at runtime. */ public final class RuntimeAttach { - private static final String AGENT_ENABLED_PROPERTY = "otel.javaagent.enabled"; - private static final String AGENT_ENABLED_ENV_VAR = "OTEL_JAVAAGENT_ENABLED"; - static final String MAIN_METHOD_CHECK_PROP = - "otel.javaagent.testing.runtime-attach.main-method-check"; - /** * Attach the OpenTelemetry Java agent to the current JVM. The attachment must be requested at the * beginning of the main method. */ public static void attachJavaagentToCurrentJVM() { - if (!shouldAttach()) { - return; - } - File javaagentFile = AgentFileProvider.getAgentFile(); - ByteBuddyAgent.attach(javaagentFile, getPid()); + CoreRuntimeAttach distroRuntimeAttach = new CoreRuntimeAttach("/otel-agent.jar"); - if (!agentIsAttached()) { - printError("Agent was not attached. An unexpected issue has happened."); - } - } - - @SuppressWarnings("SystemOut") - private static void printError(String message) { - // not using java.util.logging in order to avoid initializing the global LogManager - // too early (and incompatibly with the user's app), - // and because this is too early to use the Javaagent's PatchLogger - System.err.println(message); - } - - private static boolean shouldAttach() { - if (agentIsDisabledWithProp()) { - return false; - } - if (agentIsDisabledWithEnvVar()) { - return false; - } - if (agentIsAttached()) { - return false; - } - if (mainMethodCheckIsEnabled() && !isMainThread()) { - printError( - "Agent is not attached because runtime attachment was not requested from main thread."); - return false; - } - if (mainMethodCheckIsEnabled() && !isMainMethod()) { - printError( - "Agent is not attached because runtime attachment was not requested from main method."); - return false; - } - return true; - } - - private static boolean agentIsDisabledWithProp() { - String agentEnabledPropValue = System.getProperty(AGENT_ENABLED_PROPERTY); - return "false".equalsIgnoreCase(agentEnabledPropValue); - } - - private static boolean agentIsDisabledWithEnvVar() { - String agentEnabledEnvVarValue = System.getenv(AGENT_ENABLED_ENV_VAR); - return "false".equals(agentEnabledEnvVarValue); - } - - private static boolean agentIsAttached() { - try { - Class.forName("io.opentelemetry.javaagent.OpenTelemetryAgent", false, null); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - private static boolean mainMethodCheckIsEnabled() { - String mainThreadCheck = System.getProperty(MAIN_METHOD_CHECK_PROP); - return !"false".equals(mainThreadCheck); - } - - private static boolean isMainThread() { - Thread currentThread = Thread.currentThread(); - return "main".equals(currentThread.getName()); - } - - static boolean isMainMethod() { - StackTraceElement bottomOfStack = findBottomOfStack(Thread.currentThread()); - String methodName = bottomOfStack.getMethodName(); - return "main".equals(methodName); - } - - private static StackTraceElement findBottomOfStack(Thread thread) { - StackTraceElement[] stackTrace = thread.getStackTrace(); - return stackTrace[stackTrace.length - 1]; - } - - private static String getPid() { - return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + distroRuntimeAttach.attachJavaagentToCurrentJVM(); } private RuntimeAttach() {} diff --git a/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java b/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java index 24fb0e01..fe84c03a 100644 --- a/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java +++ b/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java @@ -12,7 +12,7 @@ public class AbstractAttachmentTest { @BeforeAll static void disableMainThreadCheck() { - System.setProperty(RuntimeAttach.MAIN_METHOD_CHECK_PROP, "false"); + System.setProperty(CoreRuntimeAttach.MAIN_METHOD_CHECK_PROP, "false"); } boolean isAttached() { diff --git a/settings.gradle.kts b/settings.gradle.kts index a54098cb..a0144715 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -47,6 +47,7 @@ include(":micrometer-meter-provider") include(":jmx-metrics") include(":maven-extension") include(":runtime-attach") +include(":runtime-attach-core") include(":samplers") include(":static-instrumenter:agent-instrumenter") include(":static-instrumenter:gradle-plugin")