Skip to content

Commit 4645bec

Browse files
jeanbisuttitrask
andauthored
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 <[email protected]> * Renaming Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 48db05a commit 4645bec

File tree

8 files changed

+177
-123
lines changed

8 files changed

+177
-123
lines changed

runtime-attach-core/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Runtime attachment core
2+
3+
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.
4+
5+
You can find [here](../runtime-attach/README.md) the project of the distribution published by OpenTelemetry.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
id("otel.publish-conventions")
4+
}
5+
6+
description = "To help in create an OpenTelemetry distro able to runtime attach an OpenTelemetry Java Instrumentation agent"
7+
8+
dependencies {
9+
implementation("net.bytebuddy:byte-buddy-agent:1.11.18")
10+
11+
// Used by byte-buddy but not brought in as a transitive dependency.
12+
compileOnly("com.google.code.findbugs:annotations")
13+
}

runtime-attach/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java renamed to runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/AgentFileProvider.java

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,25 @@
55

66
package io.opentelemetry.contrib.attach;
77

8-
import io.opentelemetry.javaagent.OpenTelemetryAgent;
98
import java.io.File;
109
import java.io.IOException;
1110
import java.io.InputStream;
12-
import java.net.URL;
1311
import java.nio.file.Files;
1412
import java.nio.file.Path;
15-
import java.security.CodeSource;
1613

1714
final class AgentFileProvider {
1815

19-
static File getAgentFile() {
16+
private final String agentJarResourceName;
2017

21-
verifyExistenceOfAgentJarFile();
18+
AgentFileProvider(String agentJarResourceName) {
19+
this.agentJarResourceName = agentJarResourceName;
20+
}
21+
22+
File getAgentFile() {
2223

2324
Path tempDirPath = createTempDir();
2425

25-
Path tempAgentJarPath = createTempAgentJarFile(tempDirPath);
26+
Path tempAgentJarPath = createTempAgentJarFileIn(tempDirPath);
2627

2728
deleteTempDirOnJvmExit(tempDirPath, tempAgentJarPath);
2829

@@ -34,13 +35,6 @@ private static void deleteTempDirOnJvmExit(Path tempDirPath, Path tempAgentJarPa
3435
tempDirPath.toFile().deleteOnExit();
3536
}
3637

37-
private static void verifyExistenceOfAgentJarFile() {
38-
CodeSource codeSource = OpenTelemetryAgent.class.getProtectionDomain().getCodeSource();
39-
if (codeSource == null) {
40-
throw new IllegalStateException("could not get agent jar location");
41-
}
42-
}
43-
4438
private static Path createTempDir() {
4539
Path tempDir;
4640
try {
@@ -51,23 +45,18 @@ private static Path createTempDir() {
5145
return tempDir;
5246
}
5347

54-
private static Path createTempAgentJarFile(Path tempDir) {
55-
URL url = OpenTelemetryAgent.class.getProtectionDomain().getCodeSource().getLocation();
56-
try {
57-
return copyTo(url, tempDir, "agent.jar");
48+
private Path createTempAgentJarFileIn(Path tempDir) {
49+
Path agentJarPath = tempDir.resolve("agent.jar");
50+
try (InputStream jarAsInputStream =
51+
AgentFileProvider.class.getResourceAsStream(this.agentJarResourceName)) {
52+
if (jarAsInputStream == null) {
53+
throw new IllegalStateException(this.agentJarResourceName + " resource can't be found");
54+
}
55+
Files.copy(jarAsInputStream, agentJarPath);
5856
} catch (IOException e) {
5957
throw new IllegalStateException(
6058
"Runtime attachment can't create agent jar file in temp directory", e);
6159
}
60+
return agentJarPath;
6261
}
63-
64-
private static Path copyTo(URL url, Path tempDir, String fileName) throws IOException {
65-
Path tempFile = tempDir.resolve(fileName);
66-
try (InputStream in = url.openStream()) {
67-
Files.copy(in, tempFile);
68-
}
69-
return tempFile;
70-
}
71-
72-
private AgentFileProvider() {}
7362
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.attach;
7+
8+
import java.io.File;
9+
import java.lang.management.ManagementFactory;
10+
import net.bytebuddy.agent.ByteBuddyAgent;
11+
12+
/** This class allows you to attach the OpenTelemetry Java agent at runtime. */
13+
public final class CoreRuntimeAttach {
14+
15+
private static final String AGENT_ENABLED_PROPERTY = "otel.javaagent.enabled";
16+
private static final String AGENT_ENABLED_ENV_VAR = "OTEL_JAVAAGENT_ENABLED";
17+
static final String MAIN_METHOD_CHECK_PROP =
18+
"otel.javaagent.testing.runtime-attach.main-method-check";
19+
20+
private final String agentJarResourceName;
21+
22+
/**
23+
* Creates a new {@code DistroRuntimeAttach} from the resource name of the agent jar.
24+
*
25+
* @param agentJarResourceName Resource name of the agent jar.
26+
*/
27+
public CoreRuntimeAttach(String agentJarResourceName) {
28+
this.agentJarResourceName = agentJarResourceName;
29+
}
30+
31+
/**
32+
* Attach the OpenTelemetry Java agent to the current JVM. The attachment must be requested at the
33+
* beginning of the main method.
34+
*/
35+
public void attachJavaagentToCurrentJVM() {
36+
if (!shouldAttach()) {
37+
return;
38+
}
39+
40+
AgentFileProvider agentFileProvider = new AgentFileProvider(agentJarResourceName);
41+
42+
File javaagentFile = agentFileProvider.getAgentFile();
43+
ByteBuddyAgent.attach(javaagentFile, getPid());
44+
45+
if (!agentIsAttached()) {
46+
printError("Agent was not attached. An unexpected issue has happened.");
47+
}
48+
}
49+
50+
@SuppressWarnings("SystemOut")
51+
private static void printError(String message) {
52+
// not using java.util.logging in order to avoid initializing the global LogManager
53+
// too early (and incompatibly with the user's app),
54+
// and because this is too early to use the Javaagent's PatchLogger
55+
System.err.println(message);
56+
}
57+
58+
private static boolean shouldAttach() {
59+
if (agentIsDisabledWithProp()) {
60+
return false;
61+
}
62+
if (agentIsDisabledWithEnvVar()) {
63+
return false;
64+
}
65+
if (agentIsAttached()) {
66+
return false;
67+
}
68+
if (mainMethodCheckIsEnabled() && !isMainThread()) {
69+
printError(
70+
"Agent is not attached because runtime attachment was not requested from main thread.");
71+
return false;
72+
}
73+
if (mainMethodCheckIsEnabled() && !isMainMethod()) {
74+
printError(
75+
"Agent is not attached because runtime attachment was not requested from main method.");
76+
return false;
77+
}
78+
return true;
79+
}
80+
81+
private static boolean agentIsDisabledWithProp() {
82+
String agentEnabledPropValue = System.getProperty(AGENT_ENABLED_PROPERTY);
83+
return "false".equalsIgnoreCase(agentEnabledPropValue);
84+
}
85+
86+
private static boolean agentIsDisabledWithEnvVar() {
87+
String agentEnabledEnvVarValue = System.getenv(AGENT_ENABLED_ENV_VAR);
88+
return "false".equals(agentEnabledEnvVarValue);
89+
}
90+
91+
private static boolean agentIsAttached() {
92+
try {
93+
Class.forName("io.opentelemetry.javaagent.OpenTelemetryAgent", false, null);
94+
return true;
95+
} catch (ClassNotFoundException e) {
96+
return false;
97+
}
98+
}
99+
100+
private static boolean mainMethodCheckIsEnabled() {
101+
String mainThreadCheck = System.getProperty(MAIN_METHOD_CHECK_PROP);
102+
return !"false".equals(mainThreadCheck);
103+
}
104+
105+
private static boolean isMainThread() {
106+
Thread currentThread = Thread.currentThread();
107+
return "main".equals(currentThread.getName());
108+
}
109+
110+
static boolean isMainMethod() {
111+
StackTraceElement bottomOfStack = findBottomOfStack(Thread.currentThread());
112+
String methodName = bottomOfStack.getMethodName();
113+
return "main".equals(methodName);
114+
}
115+
116+
private static StackTraceElement findBottomOfStack(Thread thread) {
117+
StackTraceElement[] stackTrace = thread.getStackTrace();
118+
return stackTrace[stackTrace.length - 1];
119+
}
120+
121+
private static String getPid() {
122+
return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
123+
}
124+
}

runtime-attach/build.gradle.kts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ plugins {
33
id("otel.publish-conventions")
44
}
55

6-
description = "Utility to attach OpenTelemetry Java Instrumentation agent from classpath"
6+
description = "To runtime attach the OpenTelemetry Java Instrumentation agent"
7+
8+
val agent: Configuration by configurations.creating
79

810
dependencies {
9-
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent:1.6.0")
10-
implementation("net.bytebuddy:byte-buddy-agent:1.11.18")
11+
implementation(project(":runtime-attach-core"))
12+
agent("io.opentelemetry.javaagent:opentelemetry-javaagent:1.15.0")
1113

1214
// Used by byte-buddy but not brought in as a transitive dependency.
1315
compileOnly("com.google.code.findbugs:annotations")
@@ -23,3 +25,13 @@ tasks.test {
2325
useJUnitPlatform()
2426
setForkEvery(1) // One JVM by test class to avoid a test class launching a runtime attachment influences the behavior of another test class
2527
}
28+
29+
tasks {
30+
jar {
31+
inputs.files(agent)
32+
from({
33+
agent.singleFile
34+
})
35+
rename { "otel-agent.jar" }
36+
}
37+
}

runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,108 +5,18 @@
55

66
package io.opentelemetry.contrib.attach;
77

8-
import java.io.File;
9-
import java.lang.management.ManagementFactory;
10-
import net.bytebuddy.agent.ByteBuddyAgent;
11-
128
/** This class allows you to attach the OpenTelemetry Java agent at runtime. */
139
public final class RuntimeAttach {
1410

15-
private static final String AGENT_ENABLED_PROPERTY = "otel.javaagent.enabled";
16-
private static final String AGENT_ENABLED_ENV_VAR = "OTEL_JAVAAGENT_ENABLED";
17-
static final String MAIN_METHOD_CHECK_PROP =
18-
"otel.javaagent.testing.runtime-attach.main-method-check";
19-
2011
/**
2112
* Attach the OpenTelemetry Java agent to the current JVM. The attachment must be requested at the
2213
* beginning of the main method.
2314
*/
2415
public static void attachJavaagentToCurrentJVM() {
25-
if (!shouldAttach()) {
26-
return;
27-
}
28-
29-
File javaagentFile = AgentFileProvider.getAgentFile();
30-
ByteBuddyAgent.attach(javaagentFile, getPid());
31-
32-
if (!agentIsAttached()) {
33-
printError("Agent was not attached. An unexpected issue has happened.");
34-
}
35-
}
36-
37-
@SuppressWarnings("SystemOut")
38-
private static void printError(String message) {
39-
// not using java.util.logging in order to avoid initializing the global LogManager
40-
// too early (and incompatibly with the user's app),
41-
// and because this is too early to use the Javaagent's PatchLogger
42-
System.err.println(message);
43-
}
44-
45-
private static boolean shouldAttach() {
46-
if (agentIsDisabledWithProp()) {
47-
return false;
48-
}
49-
if (agentIsDisabledWithEnvVar()) {
50-
return false;
51-
}
52-
if (agentIsAttached()) {
53-
return false;
54-
}
55-
if (mainMethodCheckIsEnabled() && !isMainThread()) {
56-
printError(
57-
"Agent is not attached because runtime attachment was not requested from main thread.");
58-
return false;
59-
}
60-
if (mainMethodCheckIsEnabled() && !isMainMethod()) {
61-
printError(
62-
"Agent is not attached because runtime attachment was not requested from main method.");
63-
return false;
64-
}
65-
return true;
66-
}
67-
68-
private static boolean agentIsDisabledWithProp() {
69-
String agentEnabledPropValue = System.getProperty(AGENT_ENABLED_PROPERTY);
70-
return "false".equalsIgnoreCase(agentEnabledPropValue);
71-
}
7216

73-
private static boolean agentIsDisabledWithEnvVar() {
74-
String agentEnabledEnvVarValue = System.getenv(AGENT_ENABLED_ENV_VAR);
75-
return "false".equals(agentEnabledEnvVarValue);
76-
}
77-
78-
private static boolean agentIsAttached() {
79-
try {
80-
Class.forName("io.opentelemetry.javaagent.OpenTelemetryAgent", false, null);
81-
return true;
82-
} catch (ClassNotFoundException e) {
83-
return false;
84-
}
85-
}
86-
87-
private static boolean mainMethodCheckIsEnabled() {
88-
String mainThreadCheck = System.getProperty(MAIN_METHOD_CHECK_PROP);
89-
return !"false".equals(mainThreadCheck);
90-
}
91-
92-
private static boolean isMainThread() {
93-
Thread currentThread = Thread.currentThread();
94-
return "main".equals(currentThread.getName());
95-
}
96-
97-
static boolean isMainMethod() {
98-
StackTraceElement bottomOfStack = findBottomOfStack(Thread.currentThread());
99-
String methodName = bottomOfStack.getMethodName();
100-
return "main".equals(methodName);
101-
}
102-
103-
private static StackTraceElement findBottomOfStack(Thread thread) {
104-
StackTraceElement[] stackTrace = thread.getStackTrace();
105-
return stackTrace[stackTrace.length - 1];
106-
}
17+
CoreRuntimeAttach distroRuntimeAttach = new CoreRuntimeAttach("/otel-agent.jar");
10718

108-
private static String getPid() {
109-
return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
19+
distroRuntimeAttach.attachJavaagentToCurrentJVM();
11020
}
11121

11222
private RuntimeAttach() {}

runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class AbstractAttachmentTest {
1212

1313
@BeforeAll
1414
static void disableMainThreadCheck() {
15-
System.setProperty(RuntimeAttach.MAIN_METHOD_CHECK_PROP, "false");
15+
System.setProperty(CoreRuntimeAttach.MAIN_METHOD_CHECK_PROP, "false");
1616
}
1717

1818
boolean isAttached() {

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ include(":micrometer-meter-provider")
4747
include(":jmx-metrics")
4848
include(":maven-extension")
4949
include(":runtime-attach")
50+
include(":runtime-attach-core")
5051
include(":samplers")
5152
include(":static-instrumenter:agent-instrumenter")
5253
include(":static-instrumenter:gradle-plugin")

0 commit comments

Comments
 (0)