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:
parent
48db05aa9f
commit
4645beca88
|
|
@ -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.
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
@ -5,24 +5,25 @@
|
||||||
|
|
||||||
package io.opentelemetry.contrib.attach;
|
package io.opentelemetry.contrib.attach;
|
||||||
|
|
||||||
import io.opentelemetry.javaagent.OpenTelemetryAgent;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.CodeSource;
|
|
||||||
|
|
||||||
final class AgentFileProvider {
|
final class AgentFileProvider {
|
||||||
|
|
||||||
static File getAgentFile() {
|
private final String agentJarResourceName;
|
||||||
|
|
||||||
verifyExistenceOfAgentJarFile();
|
AgentFileProvider(String agentJarResourceName) {
|
||||||
|
this.agentJarResourceName = agentJarResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
File getAgentFile() {
|
||||||
|
|
||||||
Path tempDirPath = createTempDir();
|
Path tempDirPath = createTempDir();
|
||||||
|
|
||||||
Path tempAgentJarPath = createTempAgentJarFile(tempDirPath);
|
Path tempAgentJarPath = createTempAgentJarFileIn(tempDirPath);
|
||||||
|
|
||||||
deleteTempDirOnJvmExit(tempDirPath, tempAgentJarPath);
|
deleteTempDirOnJvmExit(tempDirPath, tempAgentJarPath);
|
||||||
|
|
||||||
|
|
@ -34,13 +35,6 @@ final class AgentFileProvider {
|
||||||
tempDirPath.toFile().deleteOnExit();
|
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() {
|
private static Path createTempDir() {
|
||||||
Path tempDir;
|
Path tempDir;
|
||||||
try {
|
try {
|
||||||
|
|
@ -51,23 +45,18 @@ final class AgentFileProvider {
|
||||||
return tempDir;
|
return tempDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path createTempAgentJarFile(Path tempDir) {
|
private Path createTempAgentJarFileIn(Path tempDir) {
|
||||||
URL url = OpenTelemetryAgent.class.getProtectionDomain().getCodeSource().getLocation();
|
Path agentJarPath = tempDir.resolve("agent.jar");
|
||||||
try {
|
try (InputStream jarAsInputStream =
|
||||||
return copyTo(url, tempDir, "agent.jar");
|
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) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Runtime attachment can't create agent jar file in temp directory", e);
|
"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() {}
|
|
||||||
}
|
}
|
||||||
|
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,13 @@ plugins {
|
||||||
id("otel.publish-conventions")
|
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 {
|
dependencies {
|
||||||
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent:1.6.0")
|
implementation(project(":runtime-attach-core"))
|
||||||
implementation("net.bytebuddy:byte-buddy-agent:1.11.18")
|
agent("io.opentelemetry.javaagent:opentelemetry-javaagent:1.15.0")
|
||||||
|
|
||||||
// Used by byte-buddy but not brought in as a transitive dependency.
|
// Used by byte-buddy but not brought in as a transitive dependency.
|
||||||
compileOnly("com.google.code.findbugs:annotations")
|
compileOnly("com.google.code.findbugs:annotations")
|
||||||
|
|
@ -23,3 +25,13 @@ tasks.test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
setForkEvery(1) // One JVM by test class to avoid a test class launching a runtime attachment influences the behavior of another test class
|
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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,108 +5,18 @@
|
||||||
|
|
||||||
package io.opentelemetry.contrib.attach;
|
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. */
|
/** This class allows you to attach the OpenTelemetry Java agent at runtime. */
|
||||||
public final class RuntimeAttach {
|
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
|
* Attach the OpenTelemetry Java agent to the current JVM. The attachment must be requested at the
|
||||||
* beginning of the main method.
|
* beginning of the main method.
|
||||||
*/
|
*/
|
||||||
public static void attachJavaagentToCurrentJVM() {
|
public static void attachJavaagentToCurrentJVM() {
|
||||||
if (!shouldAttach()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File javaagentFile = AgentFileProvider.getAgentFile();
|
CoreRuntimeAttach distroRuntimeAttach = new CoreRuntimeAttach("/otel-agent.jar");
|
||||||
ByteBuddyAgent.attach(javaagentFile, getPid());
|
|
||||||
|
|
||||||
if (!agentIsAttached()) {
|
distroRuntimeAttach.attachJavaagentToCurrentJVM();
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RuntimeAttach() {}
|
private RuntimeAttach() {}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public class AbstractAttachmentTest {
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void disableMainThreadCheck() {
|
static void disableMainThreadCheck() {
|
||||||
System.setProperty(RuntimeAttach.MAIN_METHOD_CHECK_PROP, "false");
|
System.setProperty(CoreRuntimeAttach.MAIN_METHOD_CHECK_PROP, "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isAttached() {
|
boolean isAttached() {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ include(":micrometer-meter-provider")
|
||||||
include(":jmx-metrics")
|
include(":jmx-metrics")
|
||||||
include(":maven-extension")
|
include(":maven-extension")
|
||||||
include(":runtime-attach")
|
include(":runtime-attach")
|
||||||
|
include(":runtime-attach-core")
|
||||||
include(":samplers")
|
include(":samplers")
|
||||||
include(":static-instrumenter:agent-instrumenter")
|
include(":static-instrumenter:agent-instrumenter")
|
||||||
include(":static-instrumenter:gradle-plugin")
|
include(":static-instrumenter:gradle-plugin")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue