Skip to content

Commit d4280c9

Browse files
authored
Fix log4j2 correlation with shaded log4j (#3764)
1 parent 030893a commit d4280c9

File tree

4 files changed

+111
-29
lines changed

4 files changed

+111
-29
lines changed

CHANGELOG.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
3131
3232
=== Unreleased
3333
34+
[float]
35+
===== Bug fixes
36+
* Fix log4j2 log correlation with shaded application jar - {pull}3764[#3764]
37+
3438
[[release-notes-1.x]]
3539
=== Java Agent version 1.x
3640

apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchers.java

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
package co.elastic.apm.agent.sdk.bytebuddy;
2020

2121
import co.elastic.apm.agent.sdk.internal.InternalUtil;
22+
import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils;
2223
import co.elastic.apm.agent.sdk.logging.Logger;
2324
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
24-
import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils;
2525
import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent;
2626
import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap;
2727
import net.bytebuddy.description.NamedElement;
@@ -31,20 +31,21 @@
3131
import javax.annotation.Nullable;
3232
import java.io.File;
3333
import java.io.IOException;
34+
import java.io.InputStream;
3435
import java.net.JarURLConnection;
3536
import java.net.URISyntaxException;
3637
import java.net.URL;
3738
import java.net.URLConnection;
3839
import java.security.CodeSource;
3940
import java.security.ProtectionDomain;
4041
import java.util.Collection;
42+
import java.util.Properties;
4143
import java.util.jar.Attributes;
4244
import java.util.jar.JarFile;
4345
import java.util.jar.Manifest;
46+
import java.util.zip.ZipEntry;
4447

45-
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
46-
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
47-
import static net.bytebuddy.matcher.ElementMatchers.none;
48+
import static net.bytebuddy.matcher.ElementMatchers.*;
4849

4950
public class CustomElementMatchers {
5051

@@ -183,15 +184,23 @@ public static <T extends NamedElement> ElementMatcher.Junction<T> isProxy() {
183184
* @param version the version to check against
184185
* @return an LTE SemVer matcher
185186
*/
186-
public static ElementMatcher.Junction<ProtectionDomain> implementationVersionLte(final String version) {
187-
return implementationVersion(version, Matcher.LTE);
187+
public static ElementMatcher.Junction<ProtectionDomain> implementationVersionLte(String version) {
188+
return implementationVersion(version, Matcher.LTE, null, null);
188189
}
189190

190-
public static ElementMatcher.Junction<ProtectionDomain> implementationVersionGte(final String version) {
191-
return implementationVersion(version, Matcher.GTE);
191+
public static ElementMatcher.Junction<ProtectionDomain> implementationVersionGte(String version) {
192+
return implementationVersion(version, Matcher.GTE, null, null);
192193
}
193194

194-
private static ElementMatcher.Junction<ProtectionDomain> implementationVersion(final String version, final Matcher matcher) {
195+
public static ElementMatcher.Junction<ProtectionDomain> implementationVersionLte(String version, String groupId, String artifactId) {
196+
return implementationVersion(version, Matcher.LTE, groupId, artifactId);
197+
}
198+
199+
public static ElementMatcher.Junction<ProtectionDomain> implementationVersionGte(String version, String groupId, String artifactId) {
200+
return implementationVersion(version, Matcher.GTE, groupId, artifactId);
201+
}
202+
203+
private static ElementMatcher.Junction<ProtectionDomain> implementationVersion(final String version, final Matcher matcher, @Nullable final String mavenGroupId, @Nullable final String mavenArtifactId) {
195204
return new ElementMatcher.Junction.AbstractBase<ProtectionDomain>() {
196205
/**
197206
* Returns true if the implementation version read from the manifest file referenced by the given
@@ -205,7 +214,7 @@ private static ElementMatcher.Junction<ProtectionDomain> implementationVersion(f
205214
@Override
206215
public boolean matches(@Nullable ProtectionDomain protectionDomain) {
207216
try {
208-
Version pdVersion = readImplementationVersionFromManifest(protectionDomain);
217+
Version pdVersion = readImplementationVersion(protectionDomain, mavenGroupId, mavenArtifactId);
209218
if (pdVersion != null) {
210219
Version limitVersion = Version.of(version);
211220
return matcher.match(pdVersion, limitVersion);
@@ -221,22 +230,51 @@ public boolean matches(@Nullable ProtectionDomain protectionDomain) {
221230
}
222231

223232
@Nullable
224-
private static Version readImplementationVersionFromManifest(@Nullable ProtectionDomain protectionDomain) throws IOException, URISyntaxException {
233+
private static Version readImplementationVersion(@Nullable ProtectionDomain protectionDomain, @Nullable String mavenGroupId, @Nullable String mavenArtifactId) throws IOException, URISyntaxException {
225234
Version version = null;
226235
JarFile jarFile = null;
236+
237+
if (protectionDomain == null) {
238+
logger.info("Cannot read implementation version - got null ProtectionDomain");
239+
return null;
240+
}
241+
227242
try {
228-
if (protectionDomain != null) {
229-
CodeSource codeSource = protectionDomain.getCodeSource();
230-
if (codeSource != null) {
231-
URL jarUrl = codeSource.getLocation();
232-
if (jarUrl != null) {
233-
// does not yet establish an actual connection
234-
URLConnection urlConnection = jarUrl.openConnection();
235-
if (urlConnection instanceof JarURLConnection) {
236-
jarFile = ((JarURLConnection) urlConnection).getJarFile();
237-
} else {
238-
jarFile = new JarFile(new File(jarUrl.toURI()));
243+
CodeSource codeSource = protectionDomain.getCodeSource();
244+
if (codeSource != null) {
245+
URL jarUrl = codeSource.getLocation();
246+
if (jarUrl != null) {
247+
// does not yet establish an actual connection
248+
URLConnection urlConnection = jarUrl.openConnection();
249+
if (urlConnection instanceof JarURLConnection) {
250+
jarFile = ((JarURLConnection) urlConnection).getJarFile();
251+
} else {
252+
jarFile = new JarFile(new File(jarUrl.toURI()));
253+
}
254+
255+
// read maven properties first as they have higher priority over manifest
256+
// this is mostly for shaded libraries in "fat-jar" applications
257+
if (mavenGroupId != null && mavenArtifactId != null) {
258+
ZipEntry zipEntry = jarFile.getEntry(String.format("META-INF/maven/%s/%s/pom.properties", mavenGroupId, mavenArtifactId));
259+
if (zipEntry != null) {
260+
Properties properties = new Properties();
261+
try (InputStream input = jarFile.getInputStream(zipEntry)) {
262+
properties.load(input);
263+
}
264+
if (mavenGroupId.equals(properties.getProperty("groupId")) && mavenArtifactId.equals(properties.getProperty("artifactId"))) {
265+
String stringVersion = properties.getProperty("version");
266+
if (stringVersion != null) {
267+
version = Version.of(stringVersion);
268+
}
269+
}
239270
}
271+
}
272+
273+
// reading manifest if library packaged as a jar
274+
//
275+
// doing this after maven properties is important as it might report executable jar version
276+
// when application is packaged as a "fat jar"
277+
if (version == null) {
240278
Manifest manifest = jarFile.getManifest();
241279
if (manifest != null) {
242280
Attributes attributes = manifest.getMainAttributes();
@@ -248,12 +286,9 @@ private static Version readImplementationVersionFromManifest(@Nullable Protectio
248286
if (manifestVersion != null) {
249287
version = Version.of(manifestVersion);
250288
}
251-
252289
}
253290
}
254291
}
255-
} else {
256-
logger.info("Cannot read implementation version - got null ProtectionDomain");
257292
}
258293
} finally {
259294
if (jarFile != null) {

apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchersTest.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,29 @@
1818
*/
1919
package co.elastic.apm.agent.sdk.bytebuddy;
2020

21-
import org.apache.http.client.HttpClient;
22-
2321
import net.bytebuddy.description.type.TypeDescription;
22+
import org.apache.http.client.HttpClient;
2423
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.io.TempDir;
2525

2626
import java.io.File;
27+
import java.io.IOException;
2728
import java.net.MalformedURLException;
2829
import java.net.URISyntaxException;
2930
import java.net.URL;
3031
import java.net.URLClassLoader;
32+
import java.net.URI;
33+
import java.nio.file.Files;
34+
import java.nio.file.FileSystem;
35+
import java.nio.file.FileSystems;
36+
import java.nio.file.Path;
37+
import java.nio.file.Paths;
3138
import java.security.CodeSigner;
3239
import java.security.CodeSource;
3340
import java.security.ProtectionDomain;
41+
import java.util.HashMap;
3442
import java.util.List;
43+
import java.util.Map;
3544

3645
import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.*;
3746
import static net.bytebuddy.matcher.ElementMatchers.none;
@@ -63,6 +72,38 @@ void testSemVerLteWithEncodedFileUrl() throws MalformedURLException, URISyntaxEx
6372
assertThat(implementationVersionLte("2.1.9").matches(protectionDomain)).isTrue();
6473
}
6574

75+
@Test
76+
void testSemVerFallbackOnMavenProperties(@TempDir Path tempDir) throws URISyntaxException, IOException {
77+
// Relying on Apache httpclient-4.5.6.jar
78+
// creates a copy of the jar without the manifest so we should parse maven properties
79+
URL originalUrl = HttpClient.class.getProtectionDomain().getCodeSource().getLocation();
80+
Path modifiedJar = tempDir.resolve("test.jar");
81+
try {
82+
Files.copy(Paths.get(originalUrl.toURI()), modifiedJar);
83+
84+
Map<String, String> fsProperties = new HashMap<>();
85+
fsProperties.put("create", "false");
86+
87+
URI fsUri = URI.create("jar:file://" + modifiedJar.toAbsolutePath());
88+
try (FileSystem zipFs = FileSystems.newFileSystem(fsUri, fsProperties)) {
89+
Files.delete(zipFs.getPath("META-INF", "MANIFEST.MF"));
90+
}
91+
92+
ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(modifiedJar.toUri().toURL(), new CodeSigner[0]), null);
93+
94+
assertThat(implementationVersionLte("4.5.5", "org.apache.httpcomponents", "httpclient").matches(protectionDomain)).isFalse();
95+
assertThat(implementationVersionLte("4.5.6", "org.apache.httpcomponents", "httpclient").matches(protectionDomain)).isTrue();
96+
assertThat(implementationVersionGte("4.5.7", "org.apache.httpcomponents", "httpclient").matches(protectionDomain)).isFalse();
97+
98+
99+
} finally {
100+
if (Files.exists(modifiedJar)) {
101+
Files.delete(modifiedJar);
102+
}
103+
}
104+
105+
}
106+
66107
private void testSemVerLteMatcher(ProtectionDomain protectionDomain) {
67108
assertThat(implementationVersionLte("3").matches(protectionDomain)).isFalse();
68109
assertThat(implementationVersionLte("3.2").matches(protectionDomain)).isFalse();

apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2LogCorrelationInstrumentation.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ public ElementMatcher<? super MethodDescription> getMethodMatcher() {
7171
}
7272

7373
public static class Log4J2_6LogCorrelationInstrumentation extends Log4j2LogCorrelationInstrumentation {
74+
7475
@Override
7576
public ElementMatcher.Junction<ProtectionDomain> getProtectionDomainPostFilter() {
76-
return implementationVersionGte("2.6").and(not(implementationVersionGte("2.7")));
77+
return implementationVersionGte("2.6", "org.apache.logging.log4j", "log4j-core")
78+
.and(not(implementationVersionGte("2.7", "org.apache.logging.log4j", "log4j-core")));
7779
}
7880

7981
public static class AdviceClass {
@@ -94,7 +96,7 @@ public static void removeFromThreadContext(@Advice.Enter boolean addedToMdc) {
9496
public static class Log4J2_7PlusLogCorrelationInstrumentation extends Log4j2LogCorrelationInstrumentation {
9597
@Override
9698
public ElementMatcher.Junction<ProtectionDomain> getProtectionDomainPostFilter() {
97-
return implementationVersionGte("2.7");
99+
return implementationVersionGte("2.7", "org.apache.logging.log4j", "log4j-core");
98100
}
99101

100102
public static class AdviceClass {

0 commit comments

Comments
 (0)