Fix "Class path contains multiple SLF4J bindings" during runtime atta… (#380)

* Fix "Class path contains multiple SLF4J bindings" during runtime attachment

* Update runtime-attach/build.gradle.kts

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>

* Renaming

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Jean Bisutti 2022-07-08 19:44:40 +02:00 committed by GitHub
parent 48db05aa9f
commit 4645beca88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 177 additions and 123 deletions

View File

@ -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.

View File

@ -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")
}

View File

@ -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() {}
}

View File

@ -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];
}
}

View File

@ -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" }
}
}

View File

@ -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() {}

View File

@ -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() {

View File

@ -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")