diff --git a/log4j-maven-shade-plugin-extensions/pom.xml b/log4j-maven-shade-plugin-extensions/pom.xml new file mode 100644 index 00000000..927c2e82 --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/pom.xml @@ -0,0 +1,233 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j-tools + 2.14.1-SNAPSHOT + ../ + + log4j-maven-shade-plugin-extensions + jar + Apache Log4j Maven Shade Plugin Transformer + Transformer implementation to concatenate Log4j2Plugins.dat files + + ${basedir}/.. + Shaded Plugin Log4j2 Transformer + /log4j-maven-plugins + org.apache.logging.log4j.maven.plugins.shade + true + + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-core + + + commons-io + commons-io + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + provided + + + + org.junit.jupiter + junit-jupiter-engine + ${junitJupiterVersion} + + + org.junit.jupiter + junit-jupiter-api + + + + org.junit.platform + junit-platform-engine + + + + test + + + org.junit.jupiter + junit-jupiter-api + ${junitJupiterVersion} + + + org.junit.platform + junit-platform-commons + + + + test + + + org.junit.platform + junit-platform-engine + ${junitPlatformVersion} + + + org.junit.platform + junit-platform-commons + + + + test + + + org.junit.platform + junit-platform-commons + ${junitPlatformVersion} + test + + + + + + + org.apache.maven.plugins + maven-remote-resources-plugin + + + + process + + + false + + + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + ${changes.plugin.version} + + + + changes-report + + + + + %URL%/%ISSUE% + true + Maven + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle.plugin.version} + + + ${log4jParentDir}/checkstyle.xml + ${log4jParentDir}/checkstyle-suppressions.xml + false + basedir=${basedir} + licensedir=${log4jParentDir}/checkstyle-header.txt + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${javadoc.plugin.version} + + Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, + and the Apache Log4j logo are trademarks of The Apache Software Foundation.

]]>
+ + false + true +
+ + + non-aggregate + + javadoc + + + +
+ + com.github.spotbugs + spotbugs-maven-plugin + + + org.apache.maven.plugins + maven-jxr-plugin + ${jxr.plugin.version} + + + non-aggregate + + jxr + + + + aggregate + + aggregate + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd.plugin.version} + + ${maven.compiler.target} + + + + org.jacoco + jacoco-maven-plugin + + + + PACKAGE + + + LINE + COVEREDRATIO + 0.96 + + + + + + +
+
+
diff --git a/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/CloseShieldOutputStream.java b/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/CloseShieldOutputStream.java new file mode 100644 index 00000000..9391a4d2 --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/CloseShieldOutputStream.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.maven.plugins.shade.transformer; + + +import org.apache.commons.io.output.ProxyOutputStream; + +import java.io.IOException; +import java.io.OutputStream; + +import static org.apache.commons.io.output.ClosedOutputStream.CLOSED_OUTPUT_STREAM; + +/** + * Suppress the close of underlying output stream. + */ +final class CloseShieldOutputStream extends ProxyOutputStream { + + /** + * @param out the OutputStream to delegate to + */ + /* default */ CloseShieldOutputStream(final OutputStream out) { + super(out); + } + + + @Override + public void close() throws IOException { + out.flush(); + out = CLOSED_OUTPUT_STREAM; + } +} diff --git a/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/Log4j2PluginCacheFileTransformer.java b/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/Log4j2PluginCacheFileTransformer.java new file mode 100644 index 00000000..801358cd --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/Log4j2PluginCacheFileTransformer.java @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.maven.plugins.shade.transformer; + +import org.apache.logging.log4j.core.config.plugins.processor.PluginCache; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +import org.apache.maven.plugins.shade.relocation.Relocator; +import org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE; + +/** + * 'log4j-maven-shade-plugin' transformer implementation. + */ +public class Log4j2PluginCacheFileTransformer + implements ReproducibleResourceTransformer { + + /** + * Log4j config files to share across the transformation stages. + */ + private final List tempFiles; + /** + * {@link Relocator} instances to share across the transformation stages. + */ + private final List tempRelocators; + /** + * Store youngest (i.e. largest millisecond) so that we can produce + * reproducible jar file + */ + private long youngestTime = 0; + + + /** + * Default constructor, initializing internal state. + */ + public Log4j2PluginCacheFileTransformer() { + tempRelocators = new ArrayList<>(); + tempFiles = new ArrayList<>(); + } + + /** + * @param resource resource to check + * @return true when resource is recognized as log4j-plugin-cache file + */ + @Override + public final boolean canTransformResource(final String resource) { + return PLUGIN_CACHE_FILE.equals(resource); + } + + @Override + @Deprecated + public void processResource(final String resource, final InputStream is, + final List relocators) { + // stub + } + + /** + * @param resource ignored parameter + * @param resourceInput resource input stream to save in temp file + * for next stage + * @param relocators relocator to keep for next stage + * @throws IOException thrown by file writing errors + */ + @Override + public final void processResource(final String resource, + final InputStream resourceInput, + final List relocators, + final long time) throws IOException { + final Path tempFile = Files.createTempFile("Log4j2Plugins", "dat"); + Files.copy(resourceInput, tempFile, REPLACE_EXISTING); + tempFiles.add(tempFile); + youngestTime = Math.max(youngestTime, time); + + if (relocators != null) { + this.tempRelocators.addAll(relocators); + } + } + + /** + * @return true when several log4j-cache-files should be merged + * or at least one relocated. + */ + @Override + public final boolean hasTransformedResource() { + return tempFiles.size() > 1 + || !tempFiles.isEmpty() && !tempRelocators.isEmpty(); + } + + + /** + * Stores all previously collected log4j-cache-files to the target jar. + * + * @param jos jar output + * @throws IOException When the IO blows up + */ + @Override + public final void modifyOutputStream(final JarOutputStream jos) + throws IOException { + try { + final PluginCache aggregator = new PluginCache(); + aggregator.loadCacheFiles(getTempFilesAsUrls()); + + relocatePlugin(tempRelocators, aggregator.getAllCategories()); + + final JarEntry jarEntry = new JarEntry(PLUGIN_CACHE_FILE); + + // Set time to youngest timestamp, to ensure reproducible output. + final FileTime fileTime = FileTime.fromMillis(youngestTime); + jarEntry.setLastModifiedTime(fileTime); + + jos.putNextEntry(jarEntry); + // prevent the aggregator to close the jar output + final CloseShieldOutputStream outputStream = + new CloseShieldOutputStream(jos); + aggregator.writeCache(outputStream); + } finally { + final ListIterator pathIterator = tempFiles.listIterator(); + while (pathIterator.hasNext()) { + final Path path = pathIterator.next(); + Files.deleteIfExists(path); + pathIterator.remove(); + } + } + } + + /** + * Applies the given {@code relocators} to the {@code aggregator}. + * + * @param relocators relocators. + * @param aggregatorCategories all categories of of the aggregator + */ + /* default */ final void relocatePlugin(final List relocators, + final Map> aggregatorCategories) { + for (final Entry> categoryEntry + : aggregatorCategories.entrySet()) { + for (final Entry pluginMapEntry + : categoryEntry.getValue().entrySet()) { + final PluginEntry pluginEntry = pluginMapEntry.getValue(); + final String originalClassName = pluginEntry.getClassName(); + + final Relocator matchingRelocator = findFirstMatchingRelocator( + originalClassName, relocators); + + if (matchingRelocator != null) { + final String newClassName = matchingRelocator + .relocateClass(originalClassName); + pluginEntry.setClassName(newClassName); + } + } + } + } + + /** + * @param originalClassName original class name + * @param relocators available relocators + * @return first matching relocator or {@code null} otherwise + */ + private Relocator findFirstMatchingRelocator(final String originalClassName, + final List relocators) { + Relocator result = null; + for (final Relocator relocator : relocators) { + if (relocator.canRelocateClass(originalClassName)) { + result = relocator; + break; + } + } + return result; + } + + + /** + * @return {@link #tempFiles} converting to the urls + * @throws MalformedURLException thrown by {@link Path} to {@link URL} + * conversion errors + */ + private Enumeration getTempFilesAsUrls() throws MalformedURLException { + final List urls = new ArrayList<>(); + for (final Path tempFile : tempFiles) { + final URL url = tempFile.toUri().toURL(); + urls.add(url); + } + return Collections.enumeration(urls); + } +} diff --git a/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/package-info.java b/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/package-info.java new file mode 100644 index 00000000..ad84f4f3 --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/src/main/java/org/apache/logging/log4j/maven/plugins/shade/transformer/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +/** + * Root package of the transformer. + */ +package org.apache.logging.log4j.maven.plugins.shade.transformer; diff --git a/log4j-maven-shade-plugin-extensions/src/main/resources/META-INF/MANIFEST.MF b/log4j-maven-shade-plugin-extensions/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e69de29b diff --git a/log4j-maven-shade-plugin-extensions/src/site/markdown/index.md b/log4j-maven-shade-plugin-extensions/src/site/markdown/index.md new file mode 100644 index 00000000..f9cba3cf --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/src/site/markdown/index.md @@ -0,0 +1,88 @@ + + + +# Log4j Maven Shaded Plugin Support + +This module provides support for [Apache Maven Shade Plugin](https://maven.apache.org/plugins/maven-shade-plugin/). + +## Introduction to the problem + +Log4j 2 is composed of plugins and they are loaded from Log4j2Plugins.dat file found in the classpath at runtime. +Shading overrides the cache files provided by each individual module. For instance, -web and -core, etc. ... +So if you happen to shade libraries providing Log4j 2 plugins, you need this thing. + + +## Overview + +This module includes the transformer for [Apache Maven Shade Plugin](https://maven.apache.org/plugins/maven-shade-plugin/), that concatenates `Log4j2Plugins.dat` files, +so it must be used when there are several Log4j2Plugins.dat files in the fat jar dependencies. + +For example a fat jar must be assembled with `org.apache.logging.log4j:log4j-web` that for sure requires also `org.apache.logging.log4j:log4j-core`. Still both includes `Log4j2Plugins.dat` resources the transformer must be configured. + +## Usage + +The transformer configuration must augment standard [Apache Maven Shade Plugin](https://maven.apache.org/plugins/maven-shade-plugin/) configuration in `pom.xml`. + +```xml + + + ... + + ... + + ... + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + ... + + + + ... + + + + + + org.apache.logging.maven + log4j-maven-shade-plugin-extensions + ${log4jVersion} + + + + + + + + +``` +In the above example `${log4jVersion}` placeholder should point to the same version of the fat jar dependencies of `org.apache.logging.log4j` group + +# Legacy + +Initially the transformer was developed in this repository https://github.com/edwgiz/maven-shaded-log4j-transformer diff --git a/log4j-maven-shade-plugin-extensions/src/site/site.xml b/log4j-maven-shade-plugin-extensions/src/site/site.xml new file mode 100644 index 00000000..55640a30 --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/src/site/site.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-maven-shade-plugin-extensions/src/test/java/org/apache/logging/log4j/maven/plugins/shade/transformer/Log4j2PluginCacheFileTransformerTest.java b/log4j-maven-shade-plugin-extensions/src/test/java/org/apache/logging/log4j/maven/plugins/shade/transformer/Log4j2PluginCacheFileTransformerTest.java new file mode 100644 index 00000000..3bf72a5b --- /dev/null +++ b/log4j-maven-shade-plugin-extensions/src/test/java/org/apache/logging/log4j/maven/plugins/shade/transformer/Log4j2PluginCacheFileTransformerTest.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.maven.plugins.shade.transformer; + +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.core.config.plugins.processor.PluginCache; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +import org.apache.maven.plugins.shade.relocation.Relocator; +import org.apache.maven.plugins.shade.relocation.SimpleRelocator; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + +import static java.util.Collections.enumeration; +import static java.util.Collections.singletonList; +import static org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + + +final class Log4j2PluginCacheFileTransformerTest { + + private static URL pluginUrl; + + @BeforeAll + public static void setUp() { + pluginUrl = Log4j2PluginCacheFileTransformerTest.class.getClassLoader().getResource(PLUGIN_CACHE_FILE); + } + + + @Test + public void testCanTransformResource() { + final Log4j2PluginCacheFileTransformer transformer = new Log4j2PluginCacheFileTransformer(); + assertFalse(transformer.canTransformResource(null)); + assertFalse(transformer.canTransformResource("")); + assertFalse(transformer.canTransformResource(".")); + assertFalse(transformer.canTransformResource("tmp.dat")); + assertFalse(transformer.canTransformResource(PLUGIN_CACHE_FILE + ".tmp")); + assertFalse(transformer.canTransformResource("tmp/" + PLUGIN_CACHE_FILE)); + assertTrue(transformer.canTransformResource(PLUGIN_CACHE_FILE)); + } + + @Test + public void test() throws Exception { + final Log4j2PluginCacheFileTransformer transformer = new Log4j2PluginCacheFileTransformer(); + long expectedYoungestResourceTime = 1605922127000L; // Sat Nov 21 2020 01:28:47 + try (InputStream log4jCacheFileInputStream = getClass().getClassLoader() + .getResourceAsStream(PLUGIN_CACHE_FILE)) { + transformer.processResource(PLUGIN_CACHE_FILE, log4jCacheFileInputStream, null, expectedYoungestResourceTime); + } + assertFalse(transformer.hasTransformedResource()); + + try (InputStream log4jCacheFileInputStream = getClass().getClassLoader() + .getResourceAsStream(PLUGIN_CACHE_FILE)) { + transformer.processResource(PLUGIN_CACHE_FILE, log4jCacheFileInputStream, null, 2000L); + } + assertTrue(transformer.hasTransformedResource()); + + assertTransformedCacheFile(transformer, expectedYoungestResourceTime, 1911442937); + } + + private void assertTransformedCacheFile( + @SuppressWarnings("SameParameterValue") Log4j2PluginCacheFileTransformer transformer, + @SuppressWarnings("SameParameterValue") long expectedTime, + @SuppressWarnings("SameParameterValue") long expectedHash) throws IOException { + final ByteArrayOutputStream jarBuff = new ByteArrayOutputStream(); + try(final JarOutputStream out = new JarOutputStream(jarBuff)) { + transformer.modifyOutputStream(out); + } + + try(JarInputStream in = new JarInputStream(new ByteArrayInputStream(jarBuff.toByteArray()))) { + for (;;) { + final JarEntry jarEntry = in.getNextJarEntry(); + if(jarEntry == null) { + fail("No expected resource in the output jar"); + } else if(jarEntry.getName().equals(PLUGIN_CACHE_FILE)) { + assertEquals(expectedTime, jarEntry.getTime()); + assertEquals(expectedHash, Arrays.hashCode(IOUtils.toByteArray(in))); + break; + } + } + } + } + + + @Test + public void testRelocation() throws IOException { + // test with matching relocator + testRelocation("org.apache.logging", "new.location.org.apache.logging", "new.location.org.apache.logging"); + + // test without matching relocator + testRelocation("com.apache.logging", "new.location.com.apache.logging", "org.apache.logging"); + } + + private void testRelocation(final String src, final String pattern, final String target) throws IOException { + final Log4j2PluginCacheFileTransformer transformer = new Log4j2PluginCacheFileTransformer(); + final Relocator log4jRelocator = new SimpleRelocator(src, pattern, null, null); + final PluginCache aggregator = new PluginCache(); + aggregator.loadCacheFiles(enumeration(singletonList(pluginUrl))); + + transformer.relocatePlugin(singletonList(log4jRelocator), aggregator.getAllCategories()); + + for (final Map pluginEntryMap : aggregator.getAllCategories().values()) { + for (final PluginEntry entry : pluginEntryMap.values()) { + assertTrue(entry.getClassName().startsWith(target)); + } + } + } + + @AfterAll + public static void tearDown() { + pluginUrl = null; + } +} \ No newline at end of file diff --git a/log4j-maven-shade-plugin-extensions/src/test/resources/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat b/log4j-maven-shade-plugin-extensions/src/test/resources/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat new file mode 100644 index 00000000..fbe6359f Binary files /dev/null and b/log4j-maven-shade-plugin-extensions/src/test/resources/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat differ diff --git a/pom.xml b/pom.xml index 07b8e015..6e639dd3 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,8 @@ FA1C814D 2.8.7 + 5.7.1 + 1.7.1 3.8.1 3.7 3.0.4 @@ -271,6 +273,11 @@ + + commons-io + commons-io + 2.11.0 + junit junit @@ -767,6 +774,7 @@ log4j-server + log4j-maven-shade-plugin-extensions