Skip to content

Commit c47a633

Browse files
committed
Log a warning and disable agent on Java 7
1 parent e59f0e7 commit c47a633

10 files changed

Lines changed: 319 additions & 1 deletion

File tree

dd-java-agent/dd-java-agent.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ configurations {
1616
sharedShadowInclude
1717
}
1818

19+
sourceCompatibility = JavaVersion.VERSION_1_7
20+
targetCompatibility = JavaVersion.VERSION_1_7
21+
1922
/*
2023
* 7 shadow jars are created
2124
* - The main "dd-java-agent" jar that also has the bootstrap project

dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.File;
66
import java.io.IOException;
77
import java.io.InputStreamReader;
8+
import java.io.PrintStream;
89
import java.lang.instrument.Instrumentation;
910
import java.lang.management.ManagementFactory;
1011
import java.lang.reflect.Field;
@@ -48,6 +49,10 @@ public static void premain(final String agentArgs, final Instrumentation inst) {
4849
}
4950

5051
public static void agentmain(final String agentArgs, final Instrumentation inst) {
52+
if (checkAndLogIfLessThanJava8()) {
53+
return;
54+
}
55+
5156
try {
5257
final URL agentJarURL = installAgentJar(inst);
5358

@@ -65,7 +70,62 @@ public static void agentmain(final String agentArgs, final Instrumentation inst)
6570
}
6671
}
6772

73+
private static boolean checkAndLogIfLessThanJava8() {
74+
return checkAndLogIfLessThanJava8(System.getProperty("java.version"), System.out);
75+
}
76+
77+
// Reachable for testing
78+
static boolean checkAndLogIfLessThanJava8(String version, PrintStream output) {
79+
if (parseJavaMajorVersion(version) < 8) {
80+
String agentVersion = "This version"; // If we can't find the agent version
81+
try {
82+
agentVersion = AgentJar.getAgentVersion();
83+
agentVersion = "Version " + agentVersion;
84+
} catch (IOException ignored) {
85+
}
86+
output.println(
87+
"Warning: "
88+
+ agentVersion
89+
+ " of dd-java-agent is not compatible with Java "
90+
+ version
91+
+ " and will not be installed.");
92+
output.println(
93+
"Please upgrade your Java version to 8+ or use the 0.x version of dd-java-agent in your build tool or download it from https://dtdg.co/java-tracer-v0");
94+
return true;
95+
}
96+
return false;
97+
}
98+
99+
// Reachable for testing
100+
static int parseJavaMajorVersion(String version) {
101+
int major = 0;
102+
if (null == version || version.isEmpty()) {
103+
return major;
104+
}
105+
int start = 0;
106+
if (version.charAt(0) == '1'
107+
&& version.length() >= 3
108+
&& version.charAt(1) == '.'
109+
&& Character.isDigit(version.charAt(2))) {
110+
start = 2;
111+
}
112+
// Parse the major digit and be a bit lenient, allowing digits followed by any non digit
113+
for (int i = start; i < version.length(); i++) {
114+
char c = version.charAt(i);
115+
if (Character.isDigit(c)) {
116+
major *= 10;
117+
major += Character.digit(c, 10);
118+
} else {
119+
break;
120+
}
121+
}
122+
return major;
123+
}
124+
68125
public static void main(final String[] args) {
126+
if (checkAndLogIfLessThanJava8()) {
127+
return;
128+
}
69129
AgentJar.main(args);
70130
}
71131

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package datadog.trace.bootstrap
2+
3+
import spock.lang.Specification
4+
5+
class AgentBootstrapTest extends Specification {
6+
def 'parse java.version strings'() {
7+
when:
8+
def major = AgentBootstrap.parseJavaMajorVersion(version)
9+
10+
then:
11+
major == expected
12+
13+
where:
14+
version | expected
15+
null | 0
16+
'' | 0
17+
'a.0.0' | 0
18+
'0.a.0' | 0
19+
'0.0.a' | 0
20+
'1.a.0_0' | 1
21+
'1.8.a_0' | 8
22+
'1.8.0_a' | 8
23+
'1.7' | 7
24+
'1.7.0' | 7
25+
'1.7.0_221' | 7
26+
'1.8' | 8
27+
'1.8.0' | 8
28+
'1.8.0_212' | 8
29+
'1.8.0_292' | 8
30+
'9-ea' | 9
31+
'9.0.4' | 9
32+
'9.1.2' | 9
33+
'10.0.2' | 10
34+
'11' | 11
35+
'11a' | 11
36+
'11.0.6' | 11
37+
'11.0.11' | 11
38+
'12.0.2' | 12
39+
'13.0.2' | 13
40+
'14' | 14
41+
'14.0.2' | 14
42+
'15' | 15
43+
'15.0.2' | 15
44+
'16.0.1' | 16
45+
'11.0.9.1+1'| 11
46+
'11.0.6+10' | 11
47+
}
48+
49+
def 'log warning message when java version is less than 8'() {
50+
setup:
51+
def baos = new ByteArrayOutputStream()
52+
def logStream = new PrintStream(baos)
53+
54+
when:
55+
def isLowerThan8 = AgentBootstrap.checkAndLogIfLessThanJava8(version, logStream)
56+
logStream.flush()
57+
def logLines = Arrays.asList(baos.toString().split('\n'))
58+
// If the list only contains a single String and that is the empty String, then the set is empty
59+
if (logLines.size() == 1 && logLines.contains('')) {
60+
logLines = []
61+
}
62+
63+
then:
64+
isLowerThan8 == expectedLower
65+
if (!expectedLower) {
66+
assert logLines.isEmpty()
67+
} else {
68+
assert logLines.size() == 2
69+
assert logLines == [
70+
"Warning: Version ${AgentJar.getAgentVersion()} of dd-java-agent is not compatible with Java ${version} and will not be installed.",
71+
'Please upgrade your Java version to 8+ or use the 0.x version of dd-java-agent in your build tool or download it from https://dtdg.co/java-tracer-v0'
72+
]
73+
}
74+
75+
where:
76+
version | expectedLower
77+
null | true
78+
'' | true
79+
'a.0.0' | true
80+
'0.a.0' | true
81+
'0.0.a' | true
82+
'1.a.0_0' | true
83+
'1.8.a_0' | false
84+
'1.8.0_a' | false
85+
'1.7' | true
86+
'1.7.0' | true
87+
'1.7.0_221' | true
88+
'1.8' | false
89+
'9.0.4' | false
90+
'10.0.2' | false
91+
'11a' | false
92+
'15' | false
93+
'11.0.9.1+1'| false
94+
95+
}
96+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
plugins {
2+
id 'java'
3+
id 'application'
4+
}
5+
6+
java {
7+
disableAutoTargetJvm()
8+
sourceCompatibility = JavaVersion.VERSION_1_7
9+
targetCompatibility = JavaVersion.VERSION_1_7
10+
}
11+
12+
configurations {
13+
agent
14+
}
15+
16+
dependencies {
17+
agent("com.datadoghq:dd-java-agent:$version")
18+
testImplementation(platform("org.junit:junit-bom:${versions.junit5}"))
19+
testImplementation('org.junit.jupiter:junit-jupiter')
20+
}
21+
22+
test {
23+
dependsOn('jar')
24+
useJUnitPlatform()
25+
testLogging {
26+
events 'passed', 'skipped', 'failed'
27+
}
28+
jvmArgs "-Dtest.published.dependencies.agent=${configurations.agent.singleFile}"
29+
jvmArgs "-Dtest.published.dependencies.jar=${tasks.jar.archiveFile.get()}"
30+
}
31+
32+
jar {
33+
manifest {
34+
attributes('Main-Class': 'test.published.dependencies.App')
35+
}
36+
}
37+
38+
application {
39+
mainClassName = 'test.published.dependencies.App'
40+
}
41+
42+
compileTestJava {
43+
sourceCompatibility = JavaVersion.VERSION_1_8
44+
targetCompatibility = JavaVersion.VERSION_1_8
45+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package test.published.dependencies;
2+
3+
public class App {
4+
public static void main(String[] args) {
5+
System.out.println("Hello World!");
6+
for (String s: args) {
7+
System.out.println(s);
8+
}
9+
}
10+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import static org.junit.jupiter.api.Assertions.*;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.io.InputStreamReader;
7+
import java.nio.file.Files;
8+
import java.nio.file.Paths;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.regex.Pattern;
12+
import java.util.stream.Collectors;
13+
import org.junit.jupiter.api.Test;
14+
15+
public class StartWithAgentTest {
16+
17+
private static final Pattern WARNING_PATTERN = Pattern.compile("^Warning: Version [^ ]+ of dd-java-agent is not compatible with Java [^ ]+ and will not be installed\\.$");
18+
private static final String UPGRADE_MESSAGE = "Please upgrade your Java version to 8+ or use the 0.x version of dd-java-agent in your build tool or download it from https://dtdg.co/java-tracer-v0";
19+
20+
@Test
21+
void ensureThatApplicationStartsWithAgentOnJava7() throws InterruptedException, IOException {
22+
String expectedMessage = "Woho! Started on Java 7";
23+
Process process = startAndWaitForJvmWithAgentForJava("JAVA_7_HOME", expectedMessage);
24+
int exitCode = process.waitFor();
25+
List<String> output = getLines(process.getInputStream());
26+
List<String> errors = getLines(process.getErrorStream());
27+
logProcessOutput(output, errors);
28+
assertEquals(0, exitCode, "Command failed with unexpected exit code");
29+
assertTrue(output.contains(expectedMessage), "Output does not contain '" + expectedMessage + "'");
30+
assertTrue(output.stream().anyMatch(WARNING_PATTERN.asPredicate()), "Output does not contain line matching '" + WARNING_PATTERN + "'");
31+
assertTrue(output.contains(UPGRADE_MESSAGE), "Output does not contain '" + UPGRADE_MESSAGE + "'");
32+
}
33+
34+
@Test
35+
void ensureThatApplicationStartsWithAgentOnJava8() throws InterruptedException, IOException {
36+
ensureThatApplicationStartsWithoutWarning("8");
37+
}
38+
39+
@Test
40+
void ensureThatApplicationStartsWithAgentOnJava11() throws InterruptedException, IOException {
41+
ensureThatApplicationStartsWithoutWarning("11");
42+
}
43+
44+
private static void ensureThatApplicationStartsWithoutWarning(String version) throws InterruptedException, IOException {
45+
String expectedMessage = "Woho! Started on Java " + version;
46+
Process process = startAndWaitForJvmWithAgentForJava("JAVA_" + version + "_HOME", expectedMessage);
47+
int exitCode = process.waitFor();
48+
List<String> output = getLines(process.getInputStream());
49+
List<String> errors = getLines(process.getErrorStream());
50+
logProcessOutput(output, errors);
51+
assertEquals(0, exitCode, "Command failed with unexpected exit code");
52+
assertTrue(output.contains(expectedMessage), "Output does not contain '" + expectedMessage + "'");
53+
assertFalse(output.stream().anyMatch(WARNING_PATTERN.asPredicate()), "Output contains unexpected line matching '" + WARNING_PATTERN + "'");
54+
assertFalse(output.contains(UPGRADE_MESSAGE), "Output contains unexpected line '" + UPGRADE_MESSAGE + "'");
55+
}
56+
57+
private static Process startAndWaitForJvmWithAgentForJava(String javaHomeEnv, String message) throws IOException {
58+
String javaHome = System.getenv(javaHomeEnv);
59+
checkFile(javaHome, javaHomeEnv);
60+
String javaAgent = System.getProperty("test.published.dependencies.agent");
61+
checkFile(javaAgent, "test.published.dependencies.agent");
62+
String jarFile = System.getProperty("test.published.dependencies.jar");
63+
checkFile(jarFile, "test.published.dependencies.jar");
64+
65+
List<String> commandLine = new ArrayList<>();
66+
commandLine.add(Paths.get(javaHome).resolve("bin").resolve("java").toString());
67+
commandLine.add("-Xmx256M");
68+
commandLine.add("-javaagent:" + javaAgent);
69+
commandLine.add("-jar");
70+
commandLine.add(jarFile);
71+
commandLine.add(message);
72+
ProcessBuilder builder = new ProcessBuilder(commandLine);
73+
builder.environment().put("JAVA_HOME", javaHome);
74+
Process process = builder.start();
75+
return process;
76+
}
77+
78+
private static void checkFile(String file, String name) {
79+
assertNotNull(file, name + " should be set");
80+
assertFalse(file.isEmpty(), name + " should not be empty ");
81+
assertTrue(Files.exists(Paths.get(file)), name + " [" + file + "] should be an existing path");
82+
}
83+
84+
private static List<String> getLines(InputStream inputStream) {
85+
return new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.toList());
86+
}
87+
88+
private static void logProcessOutput(List<String> output, List<String> errors) {
89+
System.out.println("-------------------");
90+
System.out.println("Sub process output:");
91+
output.forEach(System.out::println);
92+
System.out.println("-------------------");
93+
System.out.println("Sub process errors:");
94+
errors.forEach(System.out::println);
95+
System.out.println("-------------------");
96+
System.out.println("Sub process done.");
97+
}
98+
}

test-published-dependencies/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ allprojects {
2424
}
2525

2626
apply from: "$sharedConfigDirectory/spotless.gradle"
27+
apply from: "$sharedConfigDirectory/dependencies.gradle"
2728
}

test-published-dependencies/ot-is-shaded/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
plugins {
2+
id 'java'
3+
}
4+
15
configurations {
26
jarfile
37
}

test-published-dependencies/ot-pulls-in-api/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ java {
99

1010
dependencies {
1111
implementation "com.datadoghq:dd-trace-ot:$version"
12-
testImplementation(platform('org.junit:junit-bom:5.8.2'))
12+
testImplementation(platform("org.junit:junit-bom:${versions.junit5}"))
1313
testImplementation('org.junit.jupiter:junit-jupiter')
1414
}
1515

test-published-dependencies/settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ rootProject.name = 'test-published-dependencies'
33
include ':all-deps-exist'
44
include ':ot-pulls-in-api'
55
include ':ot-is-shaded'
6+
include ':agent-logs-on-java-7'

0 commit comments

Comments
 (0)