Skip to content

Commit 658daad

Browse files
jeanbisuttitrask
andauthored
Runtime attachment additions (#354)
* Add tests on runtime attachment * Add log when agent is disabled with otel.javaagent.enabled property or OTEL_JAVAAGENT_ENABLED environment variable * Do not try to attach if agent is already attached * Do not try to attach if attachment is not asked for the main thread * Add log if an unexpected issue has happened during attachment * Add javadoc * Add information to the Readme * Format javadoc * Fix mistake in Readme * Update runtime-attach/build.gradle.kts Co-authored-by: Trask Stalnaker <[email protected]> * Update runtime-attach/README.md Co-authored-by: Trask Stalnaker <[email protected]> * Update runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java Co-authored-by: Trask Stalnaker <[email protected]> * Update runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java Co-authored-by: Trask Stalnaker <[email protected]> * Update runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java Co-authored-by: Trask Stalnaker <[email protected]> * Update runtime-attach/build.gradle.kts Co-authored-by: Trask Stalnaker <[email protected]> * Replace triple ``` by single ` * Log at debug level in shouldAttach() method * Log at warn level if the runtime attachment is not requested from main thread * Logger name in lower case as in OTel agent * Add space in class Javadoc * Add space in Readme * Update Javadoc of method * Update runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java Co-authored-by: Trask Stalnaker <[email protected]> * Don't attach if attachment is not requested from the main method * Inline local variable * Move opentelemetry-javaagent to dependency management Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 71bc65d commit 658daad

File tree

7 files changed

+202
-5
lines changed

7 files changed

+202
-5
lines changed

dependencyManagement/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ val DEPENDENCY_SETS = listOf(
5858
)
5959

6060
val DEPENDENCIES = listOf(
61+
"io.opentelemetry.javaagent:opentelemetry-javaagent:1.14.0",
6162
"com.google.code.findbugs:annotations:3.0.1u2",
6263
"com.google.code.findbugs:jsr305:3.0.2",
6364
"com.squareup.okhttp3:okhttp:4.9.3",

runtime-attach/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
# Runtime attachment
2+
3+
If you can't update the JVM arguments to attach the [OpenTelemetry Java agent](https://github.com/open-telemetry/opentelemetry-java-instrumentation) (_-javaagent:path/to/opentelemetry-javaagent.jar_), this project allows you to do attachment programmatically.
4+
5+
The `io.opentelemetry.contrib.attach.RuntimeAttach` class has an `attachJavaagentToCurrentJVM` method allowing to trigger the attachment of the OTel agent for Java.
6+
7+
The attachment will not be initiated in the following cases:
8+
* The `otel.javaagent.enabled` property is set to `false`
9+
* The `OTEL_JAVAAGENT_ENABLED` environment variable is set to `false`
10+
* The attachment is not requested from the _main_ thread
11+
* The attachment is not requested from the `public static void main(String[] args)` method
12+
* The agent is already attached
13+
14+
_The attachment must be requested at the beginning of the `public static void main(String[] args)` method._ We give below an example for Spring Boot applications:
15+
16+
```java
17+
@SpringBootApplication
18+
public class SpringBootApp {
19+
20+
public static void main(String[] args) {
21+
RuntimeAttach.attachJavaagentToCurrentJVM();
22+
SpringApplication.run(SpringBootApp.class, args);
23+
}
24+
25+
}
26+
```
27+
128
## Component owners
229

330
- [Nikita Salnikov-Tarnovski](https://github.com/iNikem), Splunk

runtime-attach/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,15 @@ dependencies {
1111

1212
// Used by byte-buddy but not brought in as a transitive dependency.
1313
compileOnly("com.google.code.findbugs:annotations")
14+
15+
testImplementation("io.opentelemetry.javaagent:opentelemetry-javaagent")
16+
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
17+
testImplementation("org.junit.jupiter:junit-jupiter-api")
18+
testImplementation("org.junit-pioneer:junit-pioneer")
19+
testImplementation("org.assertj:assertj-core")
20+
}
21+
22+
tasks.test {
23+
useJUnitPlatform()
24+
setForkEvery(1) // One JVM by test class to avoid a test class launching a runtime attachment influences the behavior of another test class
1425
}

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

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,99 @@
77

88
import java.io.File;
99
import java.lang.management.ManagementFactory;
10+
import java.util.logging.Logger;
1011
import net.bytebuddy.agent.ByteBuddyAgent;
1112

13+
/** This class allows you to attach the OpenTelemetry Java agent at runtime. */
1214
public final class RuntimeAttach {
1315

16+
private static final Logger logger = Logger.getLogger(RuntimeAttach.class.getName());
17+
private static final String AGENT_ENABLED_PROPERTY = "otel.javaagent.enabled";
18+
private static final String AGENT_ENABLED_ENV_VAR = "OTEL_JAVAAGENT_ENABLED";
19+
static final String MAIN_METHOD_CHECK_PROP =
20+
"otel.javaagent.testing.runtime-attach.main-method-check";
21+
22+
/**
23+
* Attach the OpenTelemetry Java agent to the current JVM. The attachment must be requested at the
24+
* beginning of the main method.
25+
*/
1426
public static void attachJavaagentToCurrentJVM() {
15-
if (agentIsDisabled()) {
27+
if (!shouldAttach()) {
1628
return;
1729
}
30+
1831
File javaagentFile = AgentFileLocator.locateAgentFile();
1932
ByteBuddyAgent.attach(javaagentFile, getPid());
33+
34+
if (!agentIsAttached()) {
35+
logger.warning("Agent was not attached. An unexpected issue has happened.");
36+
}
37+
}
38+
39+
private static boolean shouldAttach() {
40+
if (agentIsDisabledWithProp()) {
41+
logger.fine("Agent was disabled with " + AGENT_ENABLED_PROPERTY + " property.");
42+
return false;
43+
}
44+
if (agentIsDisabledWithEnvVar()) {
45+
logger.fine("Agent was disabled with " + AGENT_ENABLED_ENV_VAR + " environment variable.");
46+
return false;
47+
}
48+
if (agentIsAttached()) {
49+
logger.fine("Agent is already attached. It is not attached a second time.");
50+
return false;
51+
}
52+
if (mainMethodCheckIsEnabled() && !isMainThread()) {
53+
logger.warning(
54+
"Agent is not attached because runtime attachment was not requested from main thread.");
55+
return false;
56+
}
57+
if (mainMethodCheckIsEnabled() && !isMainMethod()) {
58+
logger.warning(
59+
"Agent is not attached because runtime attachment was not requested from main method.");
60+
return false;
61+
}
62+
return true;
63+
}
64+
65+
private static boolean agentIsDisabledWithProp() {
66+
String agentEnabledPropValue = System.getProperty(AGENT_ENABLED_PROPERTY);
67+
return "false".equalsIgnoreCase(agentEnabledPropValue);
68+
}
69+
70+
private static boolean agentIsDisabledWithEnvVar() {
71+
String agentEnabledEnvVarValue = System.getenv(AGENT_ENABLED_ENV_VAR);
72+
return "false".equals(agentEnabledEnvVarValue);
73+
}
74+
75+
private static boolean agentIsAttached() {
76+
try {
77+
Class.forName("io.opentelemetry.javaagent.OpenTelemetryAgent", false, null);
78+
return true;
79+
} catch (ClassNotFoundException e) {
80+
return false;
81+
}
82+
}
83+
84+
private static boolean mainMethodCheckIsEnabled() {
85+
String mainThreadCheck = System.getProperty(MAIN_METHOD_CHECK_PROP);
86+
return !"false".equals(mainThreadCheck);
87+
}
88+
89+
private static boolean isMainThread() {
90+
Thread currentThread = Thread.currentThread();
91+
return "main".equals(currentThread.getName());
92+
}
93+
94+
static boolean isMainMethod() {
95+
StackTraceElement bottomOfStack = findBottomOfStack(Thread.currentThread());
96+
String methodName = bottomOfStack.getMethodName();
97+
return "main".equals(methodName);
2098
}
2199

22-
private static boolean agentIsDisabled() {
23-
String enabledProperty =
24-
System.getProperty("otel.javaagent.enabled", System.getenv("OTEL_JAVAAGENT_ENABLED"));
25-
return "false".equals(enabledProperty);
100+
private static StackTraceElement findBottomOfStack(Thread thread) {
101+
StackTraceElement[] stackTrace = thread.getStackTrace();
102+
return stackTrace[stackTrace.length - 1];
26103
}
27104

28105
private static String getPid() {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.attach;
7+
8+
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.trace.Span;
9+
import org.junit.jupiter.api.BeforeAll;
10+
11+
public class AbstractAttachmentTest {
12+
13+
@BeforeAll
14+
static void disableMainThreadCheck() {
15+
System.setProperty(RuntimeAttach.MAIN_METHOD_CHECK_PROP, "false");
16+
}
17+
18+
boolean isAttached() {
19+
return Span.current().getSpanContext().isValid();
20+
}
21+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.attach;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.extension.annotations.WithSpan;
11+
import org.junit.jupiter.api.Test;
12+
import org.junitpioneer.jupiter.SetEnvironmentVariable;
13+
import org.junitpioneer.jupiter.SetSystemProperty;
14+
15+
public class AgentDisabledTest extends AbstractAttachmentTest {
16+
17+
@SetEnvironmentVariable(key = "OTEL_JAVAAGENT_ENABLED", value = "false")
18+
@Test
19+
void shouldNotAttachWhenAgentDisabledWithEnvVariable() {
20+
RuntimeAttach.attachJavaagentToCurrentJVM();
21+
verifyNoAttachment();
22+
}
23+
24+
@WithSpan
25+
void verifyNoAttachment() {
26+
assertThat(isAttached()).as("Agent should not be attached").isFalse();
27+
}
28+
29+
@SetSystemProperty(key = "otel.javaagent.enabled", value = "false")
30+
@Test
31+
void shouldNotAttachWhenAgentDisabledWithProperty() {
32+
RuntimeAttach.attachJavaagentToCurrentJVM();
33+
verifyNoAttachment();
34+
}
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.attach;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.extension.annotations.WithSpan;
11+
import org.junit.jupiter.api.Test;
12+
13+
public class RunTimeAttachBasicTest extends AbstractAttachmentTest {
14+
15+
@Test
16+
void shouldAttach() {
17+
RuntimeAttach.attachJavaagentToCurrentJVM();
18+
verifyAttachment();
19+
}
20+
21+
@WithSpan
22+
void verifyAttachment() {
23+
assertThat(isAttached()).as("Agent should be attached").isTrue();
24+
}
25+
}

0 commit comments

Comments
 (0)