This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch feature/2.x/remove-jansi in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 26d099726e03a3bbf641440111d0dbddc04ca8a1 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Mon Oct 7 13:31:39 2024 +0200 Remove JANSI dependency in `2.x` This commit: - Removes support for the outdated [Jansi 1.x](http://fusesource.github.io/jansi/) version in `Console` appender. - Rewrites `JAnsiTextRenderer`, use in the `%m{ansi}` and `%ex{ansi}` pattern converters to use our internal ANSI support instead of Jansi. Fixes #1736. --- log4j-core-test/pom.xml | 7 - .../log4j/core/test/categories/Layouts.java | 2 - .../appender/ConsoleAppenderAnsiMessagesMain.java | 5 +- .../ConsoleAppenderAnsiStyleJira180Main.java | 8 +- .../ConsoleAppenderAnsiStyleJira272Main.java | 8 +- .../ConsoleAppenderAnsiStyleJira319Main.java | 8 +- .../ConsoleAppenderAnsiStyleLayoutMain.java | 6 +- .../ConsoleAppenderAnsiStyleNameLayoutMain.java | 3 +- ...java => ConsoleAppenderAnsiXExceptionMain.java} | 15 +- .../ConsoleAppenderDefaultSuppressedThrowable.java | 11 +- .../ConsoleAppenderHighlightLayoutDefaultMain.java | 3 +- .../ConsoleAppenderHighlightLayoutMain.java | 3 +- .../appender/ConsoleAppenderJAnsiMessageMain.java | 84 ---- ...leAppenderJira1002ShortThrowableLayoutMain.java | 4 +- .../ConsoleAppenderNoAnsiStyleLayoutMain.java | 9 +- .../core/appender/JansiConsoleAppenderJira965.java | 28 -- .../core/impl/ThrowableFormatOptionsTest.java | 134 +++---- .../log4j/core/pattern/JAnsiTextRendererTest.java | 58 +++ ...rterTest.java => MessageAnsiConverterTest.java} | 4 +- .../core/pattern/MessageStyledConverterTest.java | 2 +- .../src/test/resources/log4j2-console-msg-ansi.xml | 30 -- log4j-core/pom.xml | 8 - .../log4j/core/appender/ConsoleAppender.java | 151 +++----- .../log4j/core/impl/ThrowableFormatOptions.java | 18 +- .../logging/log4j/core/layout/PatternLayout.java | 13 +- .../logging/log4j/core/pattern/AnsiEscape.java | 7 +- .../log4j/core/pattern/JAnsiTextRenderer.java | 423 ++++++++++----------- .../core/pattern/MessagePatternConverter.java | 9 +- .../org/apache/logging/log4j/core/util/Loader.java | 4 + log4j-parent/pom.xml | 7 - pom.xml | 7 - src/changelog/.2.x.x/1736_split_jansi_support.xml | 8 + .../.2.x.x/2916_rewrite_jansi_renderer.xml | 8 + src/site/antora/antora.tmpl.yml | 1 - src/site/antora/antora.yml | 1 - .../modules/ROOT/pages/manual/appenders.adoc | 43 +-- .../modules/ROOT/pages/manual/pattern-layout.adoc | 23 +- .../ROOT/pages/manual/systemproperties.adoc | 7 - .../manual/systemproperties/properties-jansi.adoc | 32 -- 39 files changed, 447 insertions(+), 755 deletions(-) diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml index 56f7fd885f..8c8cbc19bb 100644 --- a/log4j-core-test/pom.xml +++ b/log4j-core-test/pom.xml @@ -242,13 +242,6 @@ <scope>test</scope> </dependency> - <!-- Required for console color support in Windows --> - <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <scope>test</scope> - </dependency> - <!-- Used for JMS appenders (needs an implementation of course) --> <dependency> <groupId>javax.jms</groupId> diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java index 0cdc8b4540..89057c90c0 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java @@ -22,8 +22,6 @@ package org.apache.logging.log4j.core.test.categories; public interface Layouts { interface Csv {} - interface Jansi {} - interface Json {} interface Xml {} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java index 3088ca2bc1..a44d064ab4 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.core.config.Configurator; * </p> * * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiMessagesMain log4j-core/target/test-classes/log4j2-console.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiMessagesMain log4j-core/target/test-classes/log4j2-console.xml * </pre> */ public class ConsoleAppenderAnsiMessagesMain { @@ -38,8 +38,7 @@ public class ConsoleAppenderAnsiMessagesMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiMessagesMain.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - try (final LoggerContext ctx = Configurator.initialize( + try (final LoggerContext ignored = Configurator.initialize( ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console.xml")) { LOG.fatal("\u001b[1;35mFatal message.\u001b[0m"); LOG.error("\u001b[1;31mError message.\u001b[0m"); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java index dffc5b4289..bd8491c666 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java @@ -23,13 +23,13 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; /** - * Tests https://issues.apache.org/jira/browse/LOG4J2-180 + * Tests <a href="https://issues.apache.org/jira/browse/LOG4J2-180">LOG4J2-180</a> * <p> * Running from a Windows command line from the root of the project: * </p> * * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira180Main log4j-core/target/test-classes/log4j2-180.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira180Main log4j-core/target/test-classes/log4j2-180.xml * </pre> */ public class ConsoleAppenderAnsiStyleJira180Main { @@ -37,10 +37,8 @@ public class ConsoleAppenderAnsiStyleJira180Main { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleJira180Main.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - // System.out.println(System.getProperty("java.class.path")); final String config = args.length == 0 ? "target/test-classes/log4j2-180.xml" : args[0]; - try (final LoggerContext ctx = + try (final LoggerContext ignored = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { LOG.fatal("Fatal message."); LOG.error("Error message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java index 0f2844abe7..65a4ed1ee7 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java @@ -23,12 +23,12 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; /** - * Tests https://issues.apache.org/jira/browse/LOG4J2-272 + * Tests <a href="https://issues.apache.org/jira/browse/LOG4J2-272">LOG4J2-272</a> * <p> * Running from a Windows command line from the root of the project: * </p> * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira272Main log4j-core/target/test-classes/log4j2-272.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira272Main log4j-core/target/test-classes/log4j2-272.xml * </pre> */ public class ConsoleAppenderAnsiStyleJira272Main { @@ -36,10 +36,8 @@ public class ConsoleAppenderAnsiStyleJira272Main { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleJira272Main.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - // System.out.println(System.getProperty("java.class.path")); final String config = args.length == 0 ? "target/test-classes/log4j2-272.xml" : args[0]; - try (final LoggerContext ctx = + try (final LoggerContext ignored = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { LOG.fatal("Fatal message."); LOG.error("Error message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java index 48681a85f7..957a58e9bf 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java @@ -23,13 +23,13 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; /** - * Tests https://issues.apache.org/jira/browse/LOG4J2-319 + * Tests <a href="https://issues.apache.org/jira/browse/LOG4J2-319">LOG4J2-319</a> * <p> * Running from a Windows command line from the root of the project: * </p> * * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira319Main log4j-core/target/test-classes/log4j2-319.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira319Main log4j-core/target/test-classes/log4j2-319.xml * </pre> */ public class ConsoleAppenderAnsiStyleJira319Main { @@ -37,10 +37,8 @@ public class ConsoleAppenderAnsiStyleJira319Main { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleJira319Main.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - // System.out.println(System.getProperty("java.class.path")); final String config = args.length == 0 ? "target/test-classes/log4j2-319.xml" : args[0]; - try (final LoggerContext ctx = + try (final LoggerContext ignored = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { LOG.fatal("Fatal message."); LOG.error("Error message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java index 87730d9a5a..2ef1c52c9f 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java @@ -35,7 +35,7 @@ import org.junit.Test; * </pre> * or: * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml * </pre> * */ @@ -54,11 +54,9 @@ public class ConsoleAppenderAnsiStyleLayoutMain { } public void test(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - // System.out.println(System.getProperty("java.class.path")); final String config = args == null || args.length == 0 ? "target/test-classes/log4j2-console-style-ansi.xml" : args[0]; - try (final LoggerContext ctx = + try (final LoggerContext ignored = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { final Logger logger = LogManager.getLogger(ConsoleAppenderAnsiStyleLayoutMain.class); logger.fatal("Fatal message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java index 9088b75d47..5cdc81ee45 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java @@ -31,8 +31,7 @@ public class ConsoleAppenderAnsiStyleNameLayoutMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleNameLayoutMain.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - try (final LoggerContext ctx = Configurator.initialize( + try (final LoggerContext ignored = Configurator.initialize( ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-style-name-ansi.xml")) { LOG.fatal("Fatal message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiXExceptionMain.java similarity index 78% rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiXExceptionMain.java index d681e02c75..ee062add98 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiXExceptionMain.java @@ -22,9 +22,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.core.test.categories.Layouts; import org.junit.Test; -import org.junit.experimental.categories.Category; /** * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest @@ -34,21 +32,20 @@ import org.junit.experimental.categories.Category; * </p> * * <pre> - * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiXExceptionMain test + * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiXExceptionMain test * </pre> * * or, on Windows: * * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%USERPROFILE%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiXExceptionMain log4j-core/src/test/resources/log4j2-console-xex-ansi.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiXExceptionMain log4j-core/src/test/resources/log4j2-console-xex-ansi.xml * </pre> * */ -@Category(Layouts.Jansi.class) -public class ConsoleAppenderJAnsiXExceptionMain { +public class ConsoleAppenderAnsiXExceptionMain { public static void main(final String[] args) { - new ConsoleAppenderJAnsiXExceptionMain().test(args); + new ConsoleAppenderAnsiXExceptionMain().test(args); } /** @@ -60,12 +57,10 @@ public class ConsoleAppenderJAnsiXExceptionMain { } public void test(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - // System.out.println(System.getProperty("java.class.path")); final String config = args == null || args.length == 0 ? "target/test-classes/log4j2-console-xex-ansi.xml" : args[0]; final LoggerContext ctx = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config); - final Logger logger = LogManager.getLogger(ConsoleAppenderJAnsiXExceptionMain.class); + final Logger logger = LogManager.getLogger(ConsoleAppenderAnsiXExceptionMain.class); try { Files.getFileStore(Paths.get("?BOGUS?")); } catch (final Exception e) { diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java index 8553eb3f1d..2432df32c9 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java @@ -31,7 +31,7 @@ import org.apache.logging.log4j.core.config.Configurator; * </p> * * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml * </pre> */ public class ConsoleAppenderDefaultSuppressedThrowable { @@ -41,12 +41,11 @@ public class ConsoleAppenderDefaultSuppressedThrowable { public static void main(final String[] args) { final String config = args.length == 0 ? "target/test-classes/log4j2-console-default-suppressed-throwable.xml" : args[0]; - test(args, config); + test(config); } - static void test(final String[] args, final String config) { - // System.out.println(System.getProperty("java.class.path")); - try (final LoggerContext ctx = + static void test(final String config) { + try (final LoggerContext ignored = Configurator.initialize(ConsoleAppenderDefaultSuppressedThrowable.class.getName(), config)) { final IOException ioEx = new IOException("test suppressed"); ioEx.addSuppressed(new IOException("test suppressed 1", new IOException("test 1"))); @@ -55,8 +54,6 @@ public class ConsoleAppenderDefaultSuppressedThrowable { ioEx.addSuppressed(new IOException("test suppressed 2", ioEx2)); final IOException e = new IOException("test", ioEx); LOG.error("Error message {}, suppressed?", "Hi", e); - System.out.println("printStackTrace"); - e.printStackTrace(); } } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java index 7a1b7cf616..d9b5eeba27 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java @@ -31,8 +31,7 @@ public class ConsoleAppenderHighlightLayoutDefaultMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderHighlightLayoutDefaultMain.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - try (final LoggerContext ctx = Configurator.initialize( + try (final LoggerContext ignored = Configurator.initialize( ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-highlight-default.xml")) { LOG.fatal("Fatal message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java index dbb6958ea2..ad86c24570 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java @@ -31,8 +31,7 @@ public class ConsoleAppenderHighlightLayoutMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderHighlightLayoutMain.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - try (final LoggerContext ctx = Configurator.initialize( + try (final LoggerContext ignored = Configurator.initialize( ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-highlight.xml")) { LOG.fatal("Fatal message."); LOG.error("Error message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java deleted file mode 100644 index 56cce35a74..0000000000 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.core.appender; - -import static org.fusesource.jansi.Ansi.Color.CYAN; -import static org.fusesource.jansi.Ansi.Color.RED; -import static org.fusesource.jansi.Ansi.ansi; - -import java.util.Map.Entry; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.core.test.categories.Layouts; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.Resources; - -/** - * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest - * of the log entry (time stamp for example) is in the default color for that console. - * <p> - * Running from a Windows command line from the root of the project: - * </p> - * - * <pre> - * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiMessageMain test - * </pre> - * - * or, on Windows: - * - * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%USERPROFILE%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiMessageMain log4j-core/src/test/resources/log4j2-console-msg-ansi.xml - * </pre> - * - */ -@Category(Layouts.Jansi.class) -public class ConsoleAppenderJAnsiMessageMain { - - public static void main(final String[] args) { - new ConsoleAppenderJAnsiMessageMain().test(args); - } - - /** - * This is a @Test method to make it easy to run from a command line with {@code mvn -Dtest=FQCN test} - */ - @Test - @ResourceLock(Resources.SYSTEM_PROPERTIES) - public void test() { - test(null); - } - - public void test(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - // System.out.println(System.getProperty("java.class.path")); - final String config = - args == null || args.length == 0 ? "target/test-classes/log4j2-console-msg-ansi.xml" : args[0]; - try (final LoggerContext ctx = - Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { - final Logger logger = LogManager.getLogger(ConsoleAppenderJAnsiMessageMain.class); - logger.info(ansi().fg(RED).a("Hello").fg(CYAN).a(" World").reset()); - // JAnsi format: - // logger.info("@|red Hello|@ @|cyan World|@"); - for (final Entry<Object, Object> entry : System.getProperties().entrySet()) { - logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue()); - } - } - } -} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java index b75e3e0325..b1177acdd2 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java @@ -21,7 +21,7 @@ package org.apache.logging.log4j.core.appender; */ public class ConsoleAppenderJira1002ShortThrowableLayoutMain { - public static void main(final String[] args) { - ConsoleAppenderNoAnsiStyleLayoutMain.test(args, "target/test-classes/log4j2-1002.xml"); + public static void main() { + ConsoleAppenderNoAnsiStyleLayoutMain.test("target/test-classes/log4j2-1002.xml"); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java index 1c35103862..1165fc6a60 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.core.config.Configurator; * </p> * * <pre> - * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml + * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml * </pre> */ public class ConsoleAppenderNoAnsiStyleLayoutMain { @@ -43,12 +43,11 @@ public class ConsoleAppenderNoAnsiStyleLayoutMain { public static void main(final String[] args) { final String config = args.length == 0 ? "target/test-classes/log4j2-console-style-no-ansi.xml" : args[0]; - test(args, config); + test(config); } - static void test(final String[] args, final String config) { - // System.out.println(System.getProperty("java.class.path")); - try (final LoggerContext ctx = + static void test(final String config) { + try (final LoggerContext ignored = Configurator.initialize(ConsoleAppenderNoAnsiStyleLayoutMain.class.getName(), config)) { LOG.fatal("Fatal message."); LOG.error("Error message."); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java deleted file mode 100644 index 8bdd3e13ef..0000000000 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.core.appender; - -import org.slf4j.LoggerFactory; - -public class JansiConsoleAppenderJira965 { - - public static void main(final String[] args) { - System.out.println("Able to print on Windows"); - LoggerFactory.getLogger(JansiConsoleAppenderJira965.class); - System.out.println("Unable to print on Windows"); - } -} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java index 4719db8563..10dea80250 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java @@ -16,26 +16,27 @@ */ package org.apache.logging.log4j.core.impl; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.core.pattern.AnsiEscape; import org.apache.logging.log4j.core.pattern.JAnsiTextRenderer; import org.apache.logging.log4j.core.pattern.TextRenderer; import org.apache.logging.log4j.util.Strings; -import org.fusesource.jansi.AnsiRenderer.Code; import org.junit.jupiter.api.Test; /** * Unit tests for {@code ThrowableFormatOptions}. */ -public final class ThrowableFormatOptionsTest { +final class ThrowableFormatOptionsTest { /** * Runs a given test comparing against the expected values. @@ -71,7 +72,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable} with null options. */ @Test - public void testNull() { + void testNull() { test(null, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -79,7 +80,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable} */ @Test - public void testEmpty() { + void testEmpty() { test(new String[] {}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -87,7 +88,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{} } with null option value. */ @Test - public void testOneNullElement() { + void testOneNullElement() { test(new String[] {null}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -95,7 +96,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{} } */ @Test - public void testOneEmptyElement() { + void testOneEmptyElement() { test(new String[] {""}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -103,7 +104,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full} } */ @Test - public void testFull() { + void testFull() { test(new String[] {"full"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -111,7 +112,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{ansi} } */ @Test - public void testFullAnsi() { + void testFullAnsi() { final ThrowableFormatOptions tfo = test(new String[] {"full", "ansi"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); testFullAnsiEmptyConfig(tfo); @@ -121,7 +122,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{ansi()} } */ @Test - public void testFullAnsiEmptyConfig() { + void testFullAnsiEmptyConfig() { final ThrowableFormatOptions tfo = test(new String[] {"full", "ansi()"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); testFullAnsiEmptyConfig(tfo); @@ -130,34 +131,34 @@ public final class ThrowableFormatOptionsTest { private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map<String, Code[]> styleMap = jansiRenderer.getStyleMap(); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map<String, String> styleMap = ansiRenderer.getStyleMap(); // We have defaults assertFalse(styleMap.isEmpty()); - assertNotNull(styleMap.get("Name")); + assertNotNull(styleMap.get(toRootUpperCase("Name"))); } /** * Test {@code %throwable{full}{ansi(Warning=red))} } */ @Test - public void testFullAnsiWithCustomStyle() { + void testFullAnsiWithCustomStyle() { final ThrowableFormatOptions tfo = test(new String[] {"full", "ansi(Warning=red)"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map<String, Code[]> styleMap = jansiRenderer.getStyleMap(); - assertArrayEquals(new Code[] {Code.RED}, styleMap.get("Warning")); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map<String, String> styleMap = ansiRenderer.getStyleMap(); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); } /** * Test {@code %throwable{full}{ansi(Warning=red Key=blue Value=cyan))} } */ @Test - public void testFullAnsiWithCustomStyles() { + void testFullAnsiWithCustomStyles() { final ThrowableFormatOptions tfo = test( new String[] {"full", "ansi(Warning=red Key=blue Value=cyan)"}, Integer.MAX_VALUE, @@ -165,19 +166,19 @@ public final class ThrowableFormatOptionsTest { null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map<String, Code[]> styleMap = jansiRenderer.getStyleMap(); - assertArrayEquals(new Code[] {Code.RED}, styleMap.get("Warning")); - assertArrayEquals(new Code[] {Code.BLUE}, styleMap.get("Key")); - assertArrayEquals(new Code[] {Code.CYAN}, styleMap.get("Value")); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map<String, String> styleMap = ansiRenderer.getStyleMap(); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get(toRootUpperCase("Key"))).isEqualTo(AnsiEscape.createSequence("BLUE")); + assertThat(styleMap.get(toRootUpperCase("Value"))).isEqualTo(AnsiEscape.createSequence("CYAN")); } /** * Test {@code %throwable{full}{ansi(Warning=red Key=blue,bg_red Value=cyan,bg_black,underline)} } */ @Test - public void testFullAnsiWithCustomComplexStyles() { + void testFullAnsiWithCustomComplexStyles() { final ThrowableFormatOptions tfo = test( new String[] {"full", "ansi(Warning=red Key=blue,bg_red Value=cyan,bg_black,underline)"}, Integer.MAX_VALUE, @@ -185,19 +186,20 @@ public final class ThrowableFormatOptionsTest { null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map<String, Code[]> styleMap = jansiRenderer.getStyleMap(); - assertArrayEquals(new Code[] {Code.RED}, styleMap.get("Warning")); - assertArrayEquals(new Code[] {Code.BLUE, Code.BG_RED}, styleMap.get("Key")); - assertArrayEquals(new Code[] {Code.CYAN, Code.BG_BLACK, Code.UNDERLINE}, styleMap.get("Value")); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map<String, String> styleMap = ansiRenderer.getStyleMap(); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get(toRootUpperCase("Key"))).isEqualTo(AnsiEscape.createSequence("BLUE", "BG_RED")); + assertThat(styleMap.get(toRootUpperCase("Value"))) + .isEqualTo(AnsiEscape.createSequence("CYAN", "BG_BLACK", "UNDERLINE")); } /** * Test {@code %throwable{none} } */ @Test - public void testNone() { + void testNone() { test(new String[] {"none"}, 0, Strings.LINE_SEPARATOR, null); } @@ -205,7 +207,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{short} } */ @Test - public void testShort() { + void testShort() { test(new String[] {"short"}, 2, Strings.LINE_SEPARATOR, null); } @@ -213,7 +215,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{10} } */ @Test - public void testDepth() { + void testDepth() { test(new String[] {"10"}, 10, Strings.LINE_SEPARATOR, null); } @@ -221,7 +223,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{separator(|)} } */ @Test - public void testSeparator() { + void testSeparator() { test(new String[] {"separator(|)"}, Integer.MAX_VALUE, "|", null); } @@ -229,7 +231,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{separator()} } */ @Test - public void testSeparatorAsEmpty() { + void testSeparatorAsEmpty() { test(new String[] {"separator()"}, Integer.MAX_VALUE, Strings.EMPTY, null); } @@ -237,7 +239,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{separator(\n)} } */ @Test - public void testSeparatorAsDefaultLineSeparator() { + void testSeparatorAsDefaultLineSeparator() { test( new String[] {"separator(" + Strings.LINE_SEPARATOR + ')'}, Integer.MAX_VALUE, @@ -249,7 +251,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{separator( | )} } */ @Test - public void testSeparatorAsMultipleCharacters() { + void testSeparatorAsMultipleCharacters() { test(new String[] {"separator( | )"}, Integer.MAX_VALUE, " | ", null); } @@ -257,7 +259,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{separator(|)} } */ @Test - public void testFullAndSeparator() { + void testFullAndSeparator() { test(new String[] {"full", "separator(|)"}, Integer.MAX_VALUE, "|", null); } @@ -265,7 +267,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{filters(org.junit)}{separator(|)} } */ @Test - public void testFullAndFiltersAndSeparator() { + void testFullAndFiltersAndSeparator() { test( new String[] {"full", "filters(org.junit)", "separator(|)"}, Integer.MAX_VALUE, @@ -277,7 +279,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none}{separator(|)} } */ @Test - public void testNoneAndSeparator() { + void testNoneAndSeparator() { test(new String[] {"none", "separator(|)"}, 0, "|", null); } @@ -285,7 +287,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{short}{separator(|)} } */ @Test - public void testShortAndSeparator() { + void testShortAndSeparator() { test(new String[] {"short", "separator(|)"}, 2, "|", null); } @@ -293,7 +295,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{10}{separator(|)} } */ @Test - public void testDepthAndSeparator() { + void testDepthAndSeparator() { test(new String[] {"10", "separator(|)"}, 10, "|", null); } @@ -301,7 +303,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{filters(packages)} } */ @Test - public void testFilters() { + void testFilters() { test( new String[] {"filters(packages)"}, Integer.MAX_VALUE, @@ -313,7 +315,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{filters()} } */ @Test - public void testFiltersAsEmpty() { + void testFiltersAsEmpty() { test(new String[] {"filters()"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -321,7 +323,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{filters(package1,package2)} } */ @Test - public void testFiltersAsMultiplePackages() { + void testFiltersAsMultiplePackages() { test( new String[] {"filters(package1,package2)"}, Integer.MAX_VALUE, @@ -333,7 +335,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{filters(packages)} } */ @Test - public void testFullAndFilters() { + void testFullAndFilters() { test( new String[] {"full", "filters(packages)"}, Integer.MAX_VALUE, @@ -345,7 +347,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none}{filters(packages)} } */ @Test - public void testNoneAndFilters() { + void testNoneAndFilters() { test( new String[] {"none", "filters(packages)"}, 0, @@ -357,7 +359,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{short}{filters(packages)} } */ @Test - public void testShortAndFilters() { + void testShortAndFilters() { test( new String[] {"short", "filters(packages)"}, 2, @@ -369,7 +371,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{10}{filters(packages)} } */ @Test - public void testDepthAndFilters() { + void testDepthAndFilters() { test( new String[] {"10", "filters(packages)"}, 10, @@ -381,7 +383,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{separator(|)}{filters(packages)} } */ @Test - public void testFullAndSeparatorAndFilter() { + void testFullAndSeparatorAndFilter() { test( new String[] {"full", "separator(|)", "filters(packages)"}, Integer.MAX_VALUE, @@ -393,7 +395,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full}{separator(|)}{filters(package1,package2)} } */ @Test - public void testFullAndSeparatorAndFilters() { + void testFullAndSeparatorAndFilters() { test( new String[] {"full", "separator(|)", "filters(package1,package2)"}, Integer.MAX_VALUE, @@ -405,7 +407,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none}{separator(|)}{filters(packages)} } */ @Test - public void testNoneAndSeparatorAndFilters() { + void testNoneAndSeparatorAndFilters() { test(new String[] {"none", "separator(|)", "filters(packages)"}, 0, "|", Collections.singletonList("packages")); } @@ -413,7 +415,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{short}{separator(|)}{filters(packages)} } */ @Test - public void testShortAndSeparatorAndFilters() { + void testShortAndSeparatorAndFilters() { test( new String[] {"short", "separator(|)", "filters(packages)"}, 2, @@ -425,7 +427,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{10}{separator(|)}{filters(packages)} } */ @Test - public void testDepthAndSeparatorAndFilters() { + void testDepthAndSeparatorAndFilters() { test(new String[] {"10", "separator(|)", "filters(packages)"}, 10, "|", Collections.singletonList("packages")); } @@ -433,7 +435,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full,filters(packages)} } */ @Test - public void testSingleOptionFullAndFilters() { + void testSingleOptionFullAndFilters() { test( new String[] {"full,filters(packages)"}, Integer.MAX_VALUE, @@ -445,7 +447,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none,filters(packages)} } */ @Test - public void testSingleOptionNoneAndFilters() { + void testSingleOptionNoneAndFilters() { test(new String[] {"none,filters(packages)"}, 0, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } @@ -453,7 +455,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{short,filters(packages)} } */ @Test - public void testSingleOptionShortAndFilters() { + void testSingleOptionShortAndFilters() { test( new String[] {"short,filters(packages)"}, 2, @@ -465,7 +467,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none,filters(packages)} } */ @Test - public void testSingleOptionDepthAndFilters() { + void testSingleOptionDepthAndFilters() { test(new String[] {"10,filters(packages)"}, 10, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } @@ -473,7 +475,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{full,filters(package1,package2)} } */ @Test - public void testSingleOptionFullAndMultipleFilters() { + void testSingleOptionFullAndMultipleFilters() { test( new String[] {"full,filters(package1,package2)"}, Integer.MAX_VALUE, @@ -485,7 +487,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none,filters(package1,package2)} } */ @Test - public void testSingleOptionNoneAndMultipleFilters() { + void testSingleOptionNoneAndMultipleFilters() { test( new String[] {"none,filters(package1,package2)"}, 0, @@ -497,7 +499,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{short,filters(package1,package2)} } */ @Test - public void testSingleOptionShortAndMultipleFilters() { + void testSingleOptionShortAndMultipleFilters() { test( new String[] {"short,filters(package1,package2)"}, 2, @@ -509,7 +511,7 @@ public final class ThrowableFormatOptionsTest { * Test {@code %throwable{none,filters(package1,package2)} } */ @Test - public void testSingleOptionDepthAndMultipleFilters() { + void testSingleOptionDepthAndMultipleFilters() { test( new String[] {"10,filters(package1,package2)"}, 10, diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java new file mode 100644 index 0000000000..32a07d35f8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java @@ -0,0 +1,58 @@ +/* + * 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.core.pattern; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.presentation.HexadecimalRepresentation.HEXA_REPRESENTATION; + +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class JAnsiTextRendererTest { + + public static Stream<Arguments> testRendering() { + return Stream.of( + // Use style names + Arguments.of( + "KeyStyle=white ValueStyle=cyan,bold", + "@|KeyStyle key|@ = @|ValueStyle some value|@", + "\u001b[37mkey\u001b[m = \u001b[36;1msome value\u001b[m"), + // Use AnsiEscape codes directly + Arguments.of( + "", + "@|white key|@ = @|cyan,bold some value|@", + "\u001b[37mkey\u001b[m = \u001b[36;1msome value\u001b[m"), + // Return broken escapes as is + Arguments.of("", "Hello @|crazy|@ world!", "Hello @|crazy|@ world!"), + Arguments.of("", "Hello @|world!", "Hello @|world!")); + } + + @ParameterizedTest + @MethodSource + void testRendering(final String format, final String text, final String expected) { + final JAnsiTextRenderer renderer = new JAnsiTextRenderer(new String[] {"ansi", format}, Map.of()); + final StringBuilder actual = new StringBuilder(); + renderer.render(new StringBuilder(text), actual); + assertThat(actual.toString()) + .as("Rendering text '%s'", text) + .withRepresentation(HEXA_REPRESENTATION) + .isEqualTo(expected); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageAnsiConverterTest.java similarity index 94% rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageAnsiConverterTest.java index 294355a173..51278a04d6 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageAnsiConverterTest.java @@ -31,7 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @LoggerContextSource("log4j-message-ansi.xml") -public class MessageJansiConverterTest { +public class MessageAnsiConverterTest { private static final String EXPECTED = "\u001B[31;1mWarning!\u001B[m Pants on \u001B[31mfire!\u001B[m" + Strings.LINE_SEPARATOR; @@ -47,7 +47,7 @@ public class MessageJansiConverterTest { @Test public void testReplacement() { - // See org.fusesource.jansi.AnsiRenderer + // See https://www.javadoc.io/doc/org.jline/jline/latest/org/jline/jansi/AnsiRenderer.html logger.error("@|red,bold Warning!|@ Pants on @|red fire!|@"); final List<String> msgs = app.getMessages(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java index 71fb6fbc77..06ab681fed 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java @@ -47,7 +47,7 @@ public class MessageStyledConverterTest { @Test public void testReplacement() { - // See org.fusesource.jansi.AnsiRenderer + // See https://www.javadoc.io/doc/org.jline/jline/latest/org/jline/jansi/AnsiRenderer.html logger.error("@|WarningStyle Warning!|@ Pants on @|WarningStyle fire!|@"); final List<String> msgs = app.getMessages(); diff --git a/log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml deleted file mode 100644 index d30b0fb406..0000000000 --- a/log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - ~ 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. - --> -<Configuration status="OFF"> - <Appenders> - <Console name="Console" target="SYSTEM_OUT"> - <PatternLayout pattern="%style{%d}{white} %style{[%t]} %style{%-5level:}{yellow} %style{%msg{ansi}{KeyStyle=white ValueStyle=cyan,bold}%n%throwable}{green}" /> - </Console> - </Appenders> - <Loggers> - <Logger name="org.foo" level="DEBUG" /> - <Root level="TRACE"> - <AppenderRef ref="Console" /> - </Root> - </Loggers> -</Configuration> diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml index 1e35ebce72..f5693dc2e9 100644 --- a/log4j-core/pom.xml +++ b/log4j-core/pom.xml @@ -66,7 +66,6 @@ org.apache.commons.csv;resolution:=optional, org.apache.kafka.*;resolution:=optional, org.codehaus.stax2;resolution:=optional, - org.fusesource.jansi;resolution:=optional, org.jctools.*;resolution:=optional, org.zeromq;resolution:=optional, javax.lang.model.*;resolution:=optional, @@ -97,7 +96,6 @@ java.management;transitive=false;static=true, java.naming;transitive=false, org.apache.commons.csv;transitive=false, - org.fusesource.jansi;transitive=false, org.jspecify;transitive=false, org.zeromq.jeromq;transitive=false, <!-- A module descriptor is only available in version 1.2.16+, hence it is not detected --> @@ -194,12 +192,6 @@ <artifactId>jackson-dataformat-yaml</artifactId> <optional>true</optional> </dependency> - <!-- Required for console color support in Windows --> - <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <optional>true</optional> - </dependency> <!-- Alternative implementation of BlockingQueue using JCTools for AsyncAppender --> <dependency> <groupId>org.jctools</groupId> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java index 986c82863f..1b1bf5e2bd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java @@ -20,10 +20,7 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintStream; import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.core.Appender; @@ -35,12 +32,8 @@ import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.util.Booleans; import org.apache.logging.log4j.core.util.CloseShieldOutputStream; -import org.apache.logging.log4j.core.util.Loader; -import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.util.Chars; import org.apache.logging.log4j.util.PropertiesUtil; /** @@ -61,8 +54,7 @@ import org.apache.logging.log4j.util.PropertiesUtil; public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> { public static final String PLUGIN_NAME = "Console"; - private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; - private static ConsoleManagerFactory factory = new ConsoleManagerFactory(); + private static final ConsoleManagerFactory factory = new ConsoleManagerFactory(); private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT; private static final AtomicInteger COUNT = new AtomicInteger(); @@ -116,10 +108,10 @@ public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputSt * * @param layout The layout to use (required). * @param filter The Filter or null. - * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". + * @param target The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". * @param name The name of the Appender (required). * @param follow If true will follow changes to the underlying output stream. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they + * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they * are propagated to the caller. * @return The ConsoleAppender. * @deprecated Deprecated in 2.7; use {@link #newBuilder()}. @@ -128,22 +120,18 @@ public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputSt public static ConsoleAppender createAppender( Layout<? extends Serializable> layout, final Filter filter, - final String targetStr, + final String target, final String name, final String follow, - final String ignore) { - if (name == null) { - LOGGER.error("No name provided for ConsoleAppender"); - return null; - } - if (layout == null) { - layout = PatternLayout.createDefaultLayout(); - } - final boolean isFollow = Boolean.parseBoolean(follow); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr); - return new ConsoleAppender( - name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target, null); + final String ignoreExceptions) { + return newBuilder() + .setLayout(layout) + .setFilter(filter) + .setTarget(target == null ? DEFAULT_TARGET : Target.valueOf(target)) + .setName(name) + .setFollow(Boolean.parseBoolean(follow)) + .setIgnoreExceptions(Booleans.parseBoolean(ignoreExceptions, true)) + .build(); } /** @@ -171,21 +159,15 @@ public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputSt final boolean follow, final boolean direct, final boolean ignoreExceptions) { - // @formatter:on - if (name == null) { - LOGGER.error("No name provided for ConsoleAppender"); - return null; - } - if (layout == null) { - layout = PatternLayout.createDefaultLayout(); - } - target = target == null ? Target.SYSTEM_OUT : target; - if (follow && direct) { - LOGGER.error("Cannot use both follow and direct on ConsoleAppender"); - return null; - } - return new ConsoleAppender( - name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target, null); + return newBuilder() + .setLayout(layout) + .setFilter(filter) + .setTarget(target) + .setName(name) + .setFollow(follow) + .setDirect(direct) + .setIgnoreExceptions(ignoreExceptions) + .build(); } public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) { @@ -194,7 +176,7 @@ public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputSt "DefaultConsole-" + COUNT.incrementAndGet(), layout, null, - getDefaultManager(DEFAULT_TARGET, false, false, layout), + getDefaultManager(layout), true, DEFAULT_TARGET, null); @@ -242,85 +224,42 @@ public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputSt if (!isValid()) { return null; } - if (follow && direct) { - throw new IllegalArgumentException( - "Cannot use both follow and direct on ConsoleAppender '" + getName() + "'"); + if (direct && follow) { + LOGGER.error("Cannot use both `direct` and `follow` on ConsoleAppender."); + return null; } final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset()); + + OutputStream stream = direct + ? getDirectOutputStream(target) + : follow ? getFollowOutputStream(target) : getDefaultOutputStream(target); + + final String managerName = target.name() + '.' + follow + '.' + direct; + final OutputStreamManager manager = + OutputStreamManager.getManager(managerName, new FactoryData(stream, managerName, layout), factory); return new ConsoleAppender( - getName(), - layout, - getFilter(), - getManager(target, follow, direct, layout), - isIgnoreExceptions(), - target, - getPropertyArray()); + getName(), layout, getFilter(), manager, isIgnoreExceptions(), target, getPropertyArray()); } } - private static OutputStreamManager getDefaultManager( - final Target target, - final boolean follow, - final boolean direct, - final Layout<? extends Serializable> layout) { - final OutputStream os = getOutputStream(follow, direct, target); - + private static OutputStreamManager getDefaultManager(final Layout<? extends Serializable> layout) { + final OutputStream os = getDefaultOutputStream(ConsoleAppender.DEFAULT_TARGET); // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks. - final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get(); + final String managerName = ConsoleAppender.DEFAULT_TARGET.name() + ".false.false-" + COUNT.get(); return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); } - private static OutputStreamManager getManager( - final Target target, - final boolean follow, - final boolean direct, - final Layout<? extends Serializable> layout) { - final OutputStream os = getOutputStream(follow, direct, target); - final String managerName = target.name() + '.' + follow + '.' + direct; - return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); + private static OutputStream getDefaultOutputStream(Target target) { + return new CloseShieldOutputStream(target == Target.SYSTEM_OUT ? System.out : System.err); } - private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) { - final String enc = Charset.defaultCharset().name(); - OutputStream outputStream; - try { - // @formatter:off - outputStream = target == Target.SYSTEM_OUT - ? direct - ? new FileOutputStream(FileDescriptor.out) - : (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) - : direct - ? new FileOutputStream(FileDescriptor.err) - : (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err); - // @formatter:on - outputStream = new CloseShieldOutputStream(outputStream); - } catch (final UnsupportedEncodingException ex) { // should never happen - throw new IllegalStateException("Unsupported default encoding " + enc, ex); - } - final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); - if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) { - return outputStream; - } - try { - // We type the parameter as a wildcard to avoid a hard reference to Jansi. - final Class<?> clazz = Loader.loadClass(JANSI_CLASS); - final Constructor<?> constructor = clazz.getConstructor(OutputStream.class); - return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream)); - } catch (final ClassNotFoundException cnfe) { - LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); - } catch (final NoSuchMethodException nsme) { - LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); - } catch (final Exception ex) { - LOGGER.warn( - "Unable to instantiate {} due to {}", - JANSI_CLASS, - clean(Throwables.getRootCause(ex).toString()).trim()); - } - return outputStream; + private static OutputStream getDirectOutputStream(Target target) { + return new CloseShieldOutputStream( + new FileOutputStream(target == Target.SYSTEM_OUT ? FileDescriptor.out : FileDescriptor.err)); } - private static String clean(final String string) { - return string.replace(Chars.NUL, Chars.SPACE); + private static OutputStream getFollowOutputStream(Target target) { + return target == Target.SYSTEM_OUT ? new SystemOutStream() : new SystemErrStream(); } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java index 5df74e5d9c..7c2bb82c07 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java @@ -23,9 +23,7 @@ import org.apache.logging.log4j.core.pattern.JAnsiTextRenderer; import org.apache.logging.log4j.core.pattern.PlainTextRenderer; import org.apache.logging.log4j.core.pattern.TextRenderer; import org.apache.logging.log4j.core.util.Integers; -import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.Patterns; -import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; /** @@ -290,17 +288,11 @@ public final class ThrowableFormatOptions { || option.equalsIgnoreCase(LOCALIZED_MESSAGE)) { lines = 2; } else if (option.startsWith("ansi(") && option.endsWith(")") || option.equals("ansi")) { - if (Loader.isJansiAvailable()) { - final String styleMapStr = option.equals("ansi") - ? Strings.EMPTY - : option.substring("ansi(".length(), option.length() - 1); - ansiRenderer = new JAnsiTextRenderer( - new String[] {null, styleMapStr}, JAnsiTextRenderer.DefaultExceptionStyleMap); - } else { - StatusLogger.getLogger() - .warn( - "You requested ANSI exception rendering but JANSI is not on the classpath. Please see https://logging.apache.org/log4j/2.x/runtime-dependencies.html"); - } + final String styleMapStr = option.equals("ansi") + ? Strings.EMPTY + : option.substring("ansi(".length(), option.length() - 1); + ansiRenderer = new JAnsiTextRenderer( + new String[] {null, styleMapStr}, JAnsiTextRenderer.DEFAULT_EXCEPTION_STYLE_MAP); } else if (option.startsWith("S(") && option.endsWith(")")) { suffix = option.substring("S(".length(), option.length() - 1); } else if (option.startsWith("suffix(") && option.endsWith(")")) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java index a915cdadfb..5f6e429927 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java @@ -38,7 +38,6 @@ import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.apache.logging.log4j.core.pattern.PatternFormatter; import org.apache.logging.log4j.core.pattern.PatternParser; import org.apache.logging.log4j.core.pattern.RegexReplacement; -import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.Strings; /** @@ -652,7 +651,7 @@ public final class PatternLayout extends AbstractStringLayout { private boolean alwaysWriteExceptions = true; @PluginBuilderAttribute - private boolean disableAnsi = !useAnsiEscapeCodes(); + private boolean disableAnsi; @PluginBuilderAttribute private boolean noConsoleNoAnsi; @@ -665,13 +664,6 @@ public final class PatternLayout extends AbstractStringLayout { private Builder() {} - private boolean useAnsiEscapeCodes() { - final PropertiesUtil propertiesUtil = PropertiesUtil.getProperties(); - final boolean isPlatformSupportsAnsi = !propertiesUtil.isOsWindows(); - final boolean isJansiRequested = !propertiesUtil.getBooleanProperty("log4j.skipJansi", true); - return isPlatformSupportsAnsi || isJansiRequested; - } - /** * @param pattern * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. @@ -731,8 +723,7 @@ public final class PatternLayout extends AbstractStringLayout { /** * @param disableAnsi - * If {@code "true"} (default is value of system property `log4j.skipJansi`, or `true` if undefined), - * do not output ANSI escape codes + * If {@code true}, do not output ANSI escape codes. */ public Builder withDisableAnsi(final boolean disableAnsi) { this.disableAnsi = disableAnsi; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java index 74633b10a1..34213373df 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java @@ -421,6 +421,11 @@ public enum AnsiEscape { * @return a new map */ public static Map<String, String> createMap(final String[] values, final String[] dontEscapeKeys) { + return createMap(values, dontEscapeKeys, "\\s"); + } + + static Map<String, String> createMap( + final String[] values, final String[] dontEscapeKeys, final String separatorRegex) { final String[] sortedIgnoreKeys = dontEscapeKeys != null ? dontEscapeKeys.clone() : Strings.EMPTY_ARRAY; Arrays.sort(sortedIgnoreKeys); final Map<String, String> map = new HashMap<>(); @@ -430,7 +435,7 @@ public enum AnsiEscape { final String key = toRootUpperCase(keyValue[0]); final String value = keyValue[1]; final boolean escape = Arrays.binarySearch(sortedIgnoreKeys, key) < 0; - map.put(key, escape ? createSequence(value.split("\\s")) : value); + map.put(key, escape ? createSequence(value.split(separatorRegex)) : value); } else { LOGGER.warn("Syntax error, missing '=': Expected \"{KEY1=VALUE, KEY2=VALUE, ...}"); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java index b952957345..36d6315ce2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java @@ -16,28 +16,31 @@ */ package org.apache.logging.log4j.core.pattern; +import static org.apache.logging.log4j.core.pattern.AnsiEscape.BG_RED; +import static org.apache.logging.log4j.core.pattern.AnsiEscape.BOLD; +import static org.apache.logging.log4j.core.pattern.AnsiEscape.RED; +import static org.apache.logging.log4j.core.pattern.AnsiEscape.WHITE; +import static org.apache.logging.log4j.core.pattern.AnsiEscape.YELLOW; import static org.apache.logging.log4j.util.Strings.toRootUpperCase; -import static org.fusesource.jansi.AnsiRenderer.Code.BG_RED; -import static org.fusesource.jansi.AnsiRenderer.Code.BOLD; -import static org.fusesource.jansi.AnsiRenderer.Code.RED; -import static org.fusesource.jansi.AnsiRenderer.Code.WHITE; -import static org.fusesource.jansi.AnsiRenderer.Code.YELLOW; +import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.AnsiRenderer; -import org.fusesource.jansi.AnsiRenderer.Code; /** * Renders an input as ANSI escaped output. - * - * Uses the JAnsi rendering syntax as the default to render a message into an ANSI escaped string. - * + * <p> + * Uses the + * <a href="https://www.javadoc.io/doc/org.jline/jline/latest/org/jline/jansi/AnsiRenderer.html">JLine AnsiRenderer syntax</a> + * to render a message into an ANSI escaped string. + * </p> + * <p> * The default syntax for embedded ANSI codes is: - * + * </p> * <pre> * @|<em>code</em>(,<em>code</em>)* <em>text</em>|@ * </pre> @@ -72,279 +75,245 @@ import org.fusesource.jansi.AnsiRenderer.Code; * logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue()); * </pre> * - * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as - * Apache 2.0.) - * - * @see AnsiRenderer + * <p> + * <strong>Note:</strong> this class was originally copied and then heavily modified from + * <a href="https://www.javadoc.io/doc/org.jline/jline/latest/org/jline/jansi/AnsiRenderer.html">JAnsi/JLine AnsiRenderer</a>, + * licensed under an Apache Software License, version 2.0. + * </p> */ public final class JAnsiTextRenderer implements TextRenderer { - public static final Map<String, Code[]> DefaultExceptionStyleMap; - static final Map<String, Code[]> DefaultMessageStyleMap; - private static final Map<String, Map<String, Code[]>> PrefedinedStyleMaps; + private static final Logger LOGGER = StatusLogger.getLogger(); + + public static final Map<String, String> DEFAULT_EXCEPTION_STYLE_MAP; + static final Map<String, String> DEFAULT_MESSAGE_STYLE_MAP; + private static final Map<String, Map<String, String>> PREFEDINED_STYLE_MAPS; - private static void put(final Map<String, Code[]> map, final String name, final Code... codes) { - map.put(name, codes); + private static final String BEGIN_TOKEN = "@|"; + private static final String END_TOKEN = "|@"; + // The length of AnsiEscape.CSI + private static final int CSI_LENGTH = 2; + + private static Map.Entry<String, String> entry(final String name, final AnsiEscape... codes) { + final StringBuilder sb = new StringBuilder(AnsiEscape.CSI.getCode()); + for (final AnsiEscape code : codes) { + sb.append(code.getCode()); + } + return new AbstractMap.SimpleImmutableEntry<>(name, sb.toString()); + } + + @SafeVarargs + private static <V> Map<String, V> ofEntries(final Map.Entry<String, V>... entries) { + final Map<String, V> map = new HashMap<>(entries.length); + for (final Map.Entry<String, V> entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + return Collections.unmodifiableMap(map); } static { - final Map<String, Map<String, Code[]>> tempPreDefs = new HashMap<>(); // Default style: Spock - { - // TODO Should the keys be in an enum? - final Map<String, Code[]> map = new HashMap<>(); - put(map, "Prefix", WHITE); - put(map, "Name", BG_RED, WHITE); - put(map, "NameMessageSeparator", BG_RED, WHITE); - put(map, "Message", BG_RED, WHITE, BOLD); - put(map, "At", WHITE); - put(map, "CauseLabel", WHITE); - put(map, "Text", WHITE); - put(map, "More", WHITE); - put(map, "Suppressed", WHITE); - // StackTraceElement - put(map, "StackTraceElement.ClassLoaderName", WHITE); - put(map, "StackTraceElement.ClassLoaderSeparator", WHITE); - put(map, "StackTraceElement.ModuleName", WHITE); - put(map, "StackTraceElement.ModuleVersionSeparator", WHITE); - put(map, "StackTraceElement.ModuleVersion", WHITE); - put(map, "StackTraceElement.ModuleNameSeparator", WHITE); - put(map, "StackTraceElement.ClassName", YELLOW); - put(map, "StackTraceElement.ClassMethodSeparator", YELLOW); - put(map, "StackTraceElement.MethodName", YELLOW); - put(map, "StackTraceElement.NativeMethod", YELLOW); - put(map, "StackTraceElement.FileName", RED); - put(map, "StackTraceElement.LineNumber", RED); - put(map, "StackTraceElement.Container", RED); - put(map, "StackTraceElement.ContainerSeparator", WHITE); - put(map, "StackTraceElement.UnknownSource", RED); - // ExtraClassInfo - put(map, "ExtraClassInfo.Inexact", YELLOW); - put(map, "ExtraClassInfo.Container", YELLOW); - put(map, "ExtraClassInfo.ContainerSeparator", YELLOW); - put(map, "ExtraClassInfo.Location", YELLOW); - put(map, "ExtraClassInfo.Version", YELLOW); - // Save - DefaultExceptionStyleMap = Collections.unmodifiableMap(map); - tempPreDefs.put("Spock", DefaultExceptionStyleMap); - } + final Map<String, String> spock = ofEntries( + entry("Prefix", WHITE), + entry("Name", BG_RED, WHITE), + entry("NameMessageSeparator", BG_RED, WHITE), + entry("Message", BG_RED, WHITE, BOLD), + entry("At", WHITE), + entry("CauseLabel", WHITE), + entry("Text", WHITE), + entry("More", WHITE), + entry("Suppressed", WHITE), + // StackTraceElement + entry("StackTraceElement.ClassLoaderName", WHITE), + entry("StackTraceElement.ClassLoaderSeparator", WHITE), + entry("StackTraceElement.ModuleName", WHITE), + entry("StackTraceElement.ModuleVersionSeparator", WHITE), + entry("StackTraceElement.ModuleVersion", WHITE), + entry("StackTraceElement.ModuleNameSeparator", WHITE), + entry("StackTraceElement.ClassName", YELLOW), + entry("StackTraceElement.ClassMethodSeparator", YELLOW), + entry("StackTraceElement.MethodName", YELLOW), + entry("StackTraceElement.NativeMethod", YELLOW), + entry("StackTraceElement.FileName", RED), + entry("StackTraceElement.LineNumber", RED), + entry("StackTraceElement.Container", RED), + entry("StackTraceElement.ContainerSeparator", WHITE), + entry("StackTraceElement.UnknownSource", RED), + // ExtraClassInfo + entry("ExtraClassInfo.Inexact", YELLOW), + entry("ExtraClassInfo.Container", YELLOW), + entry("ExtraClassInfo.ContainerSeparator", YELLOW), + entry("ExtraClassInfo.Location", YELLOW), + entry("ExtraClassInfo.Version", YELLOW)); + // Style: Kirk - { - // TODO Should the keys be in an enum? - final Map<String, Code[]> map = new HashMap<>(); - put(map, "Prefix", WHITE); - put(map, "Name", BG_RED, YELLOW, BOLD); - put(map, "NameMessageSeparator", BG_RED, YELLOW); - put(map, "Message", BG_RED, WHITE, BOLD); - put(map, "At", WHITE); - put(map, "CauseLabel", WHITE); - put(map, "Text", WHITE); - put(map, "More", WHITE); - put(map, "Suppressed", WHITE); - // StackTraceElement - put(map, "StackTraceElement.ClassLoaderName", WHITE); - put(map, "StackTraceElement.ClassLoaderSeparator", WHITE); - put(map, "StackTraceElement.ModuleName", WHITE); - put(map, "StackTraceElement.ModuleVersionSeparator", WHITE); - put(map, "StackTraceElement.ModuleVersion", WHITE); - put(map, "StackTraceElement.ModuleNameSeparator", WHITE); - put(map, "StackTraceElement.ClassName", BG_RED, WHITE); - put(map, "StackTraceElement.ClassMethodSeparator", BG_RED, YELLOW); - put(map, "StackTraceElement.MethodName", BG_RED, YELLOW); - put(map, "StackTraceElement.NativeMethod", BG_RED, YELLOW); - put(map, "StackTraceElement.FileName", RED); - put(map, "StackTraceElement.LineNumber", RED); - put(map, "StackTraceElement.Container", RED); - put(map, "StackTraceElement.ContainerSeparator", WHITE); - put(map, "StackTraceElement.UnknownSource", RED); - // ExtraClassInfo - put(map, "ExtraClassInfo.Inexact", YELLOW); - put(map, "ExtraClassInfo.Container", WHITE); - put(map, "ExtraClassInfo.ContainerSeparator", WHITE); - put(map, "ExtraClassInfo.Location", YELLOW); - put(map, "ExtraClassInfo.Version", YELLOW); - // Save - tempPreDefs.put("Kirk", Collections.unmodifiableMap(map)); - } - { - final Map<String, Code[]> temp = new HashMap<>(); - // TODO - DefaultMessageStyleMap = Collections.unmodifiableMap(temp); - } - PrefedinedStyleMaps = Collections.unmodifiableMap(tempPreDefs); + final Map<String, String> kirk = ofEntries( + entry("Prefix", WHITE), + entry("Name", BG_RED, YELLOW, BOLD), + entry("NameMessageSeparator", BG_RED, YELLOW), + entry("Message", BG_RED, WHITE, BOLD), + entry("At", WHITE), + entry("CauseLabel", WHITE), + entry("Text", WHITE), + entry("More", WHITE), + entry("Suppressed", WHITE), + // StackTraceElement + entry("StackTraceElement.ClassLoaderName", WHITE), + entry("StackTraceElement.ClassLoaderSeparator", WHITE), + entry("StackTraceElement.ModuleName", WHITE), + entry("StackTraceElement.ModuleVersionSeparator", WHITE), + entry("StackTraceElement.ModuleVersion", WHITE), + entry("StackTraceElement.ModuleNameSeparator", WHITE), + entry("StackTraceElement.ClassName", BG_RED, WHITE), + entry("StackTraceElement.ClassMethodSeparator", BG_RED, YELLOW), + entry("StackTraceElement.MethodName", BG_RED, YELLOW), + entry("StackTraceElement.NativeMethod", BG_RED, YELLOW), + entry("StackTraceElement.FileName", RED), + entry("StackTraceElement.LineNumber", RED), + entry("StackTraceElement.Container", RED), + entry("StackTraceElement.ContainerSeparator", WHITE), + entry("StackTraceElement.UnknownSource", RED), + // ExtraClassInfo + entry("ExtraClassInfo.Inexact", YELLOW), + entry("ExtraClassInfo.Container", WHITE), + entry("ExtraClassInfo.ContainerSeparator", WHITE), + entry("ExtraClassInfo.Location", YELLOW), + entry("ExtraClassInfo.Version", YELLOW)); + + // Save + DEFAULT_EXCEPTION_STYLE_MAP = spock; + DEFAULT_MESSAGE_STYLE_MAP = Collections.emptyMap(); + Map<String, Map<String, String>> predefinedStyleMaps = new HashMap<>(); + predefinedStyleMaps.put("Spock", spock); + predefinedStyleMaps.put("Kirk", kirk); + PREFEDINED_STYLE_MAPS = Collections.unmodifiableMap(predefinedStyleMaps); } private final String beginToken; private final int beginTokenLen; private final String endToken; private final int endTokenLen; - private final Map<String, Code[]> styleMap; + private final Map<String, String> styleMap; - public JAnsiTextRenderer(final String[] formats, final Map<String, Code[]> defaultStyleMap) { - String tempBeginToken = AnsiRenderer.BEGIN_TOKEN; - String tempEndToken = AnsiRenderer.END_TOKEN; - final Map<String, Code[]> map; + public JAnsiTextRenderer(final String[] formats, final Map<String, String> defaultStyleMap) { + // The format string is a list of whitespace-separated expressions: + // Key=AnsiEscape(,AnsiEscape)* if (formats.length > 1) { - final String allStylesStr = formats[1]; - // Style def split - final String[] allStyleAssignmentsArr = allStylesStr.split(" "); - map = new HashMap<>(allStyleAssignmentsArr.length + defaultStyleMap.size()); - map.putAll(defaultStyleMap); - for (final String styleAssignmentStr : allStyleAssignmentsArr) { - final String[] styleAssignmentArr = styleAssignmentStr.split("="); - if (styleAssignmentArr.length != 2) { - StatusLogger.getLogger() - .warn( - "{} parsing style \"{}\", expected format: StyleName=Code(,Code)*", - getClass().getSimpleName(), - styleAssignmentStr); + final String stylesStr = formats[1]; + final Map<String, String> map = AnsiEscape.createMap( + stylesStr.split("\\s", -1), new String[] {"BeginToken", "EndToken", "Style"}, ","); + + // Handle the special tokens + beginToken = Objects.toString(map.remove("BeginToken"), BEGIN_TOKEN); + endToken = Objects.toString(map.remove("EndToken"), END_TOKEN); + final String predefinedStyle = map.remove("Style"); + + // Create style map + final Map<String, String> styleMap = new HashMap<>(map.size() + defaultStyleMap.size()); + defaultStyleMap.forEach((k, v) -> styleMap.put(toRootUpperCase(k), v)); + if (predefinedStyle != null) { + final Map<String, String> predefinedMap = PREFEDINED_STYLE_MAPS.get(predefinedStyle); + if (predefinedMap != null) { + map.putAll(predefinedMap); } else { - final String styleName = styleAssignmentArr[0]; - final String codeListStr = styleAssignmentArr[1]; - final String[] codeNames = codeListStr.split(","); - if (codeNames.length == 0) { - StatusLogger.getLogger() - .warn( - "{} parsing style \"{}\", expected format: StyleName=Code(,Code)*", - getClass().getSimpleName(), - styleAssignmentStr); - } else { - switch (styleName) { - case "BeginToken": - tempBeginToken = codeNames[0]; - break; - case "EndToken": - tempEndToken = codeNames[0]; - break; - case "StyleMapName": - final String predefinedMapName = codeNames[0]; - final Map<String, Code[]> predefinedMap = PrefedinedStyleMaps.get(predefinedMapName); - if (predefinedMap != null) { - map.putAll(predefinedMap); - } else { - StatusLogger.getLogger() - .warn( - "Unknown predefined map name {}, pick one of {}", - predefinedMapName, - null); - } - break; - default: - final Code[] codes = new Code[codeNames.length]; - for (int i = 0; i < codes.length; i++) { - codes[i] = toCode(codeNames[i]); - } - map.put(styleName, codes); - } - } + LOGGER.warn( + "Unknown predefined map name {}, pick one of {}", + predefinedStyle, + PREFEDINED_STYLE_MAPS.keySet()); } } + styleMap.putAll(map); + this.styleMap = Collections.unmodifiableMap(styleMap); } else { - map = defaultStyleMap; - } - styleMap = map; - beginToken = tempBeginToken; - endToken = tempEndToken; - beginTokenLen = tempBeginToken.length(); - endTokenLen = tempEndToken.length(); - } - - public Map<String, Code[]> getStyleMap() { - return styleMap; - } - - private void render(final Ansi ansi, final Code code) { - if (code.isColor()) { - if (code.isBackground()) { - ansi.bg(code.getColor()); - } else { - ansi.fg(code.getColor()); - } - } else if (code.isAttribute()) { - ansi.a(code.getAttribute()); - } - } - - private void render(final Ansi ansi, final Code... codes) { - for (final Code code : codes) { - render(ansi, code); + beginToken = BEGIN_TOKEN; + endToken = END_TOKEN; + this.styleMap = Collections.unmodifiableMap(defaultStyleMap); } + beginTokenLen = beginToken.length(); + endTokenLen = endToken.length(); } /** - * Renders the given text with the given names which can be ANSI code names or Log4j style names. + * Renders the given input with the given names which can be ANSI code names or Log4j style names. * - * @param text - * The text to render - * @param names + * @param input + * The input to render + * @param styleNames * ANSI code names or Log4j style names. - * @return A rendered string containing ANSI codes. */ - private String render(final String text, final String... names) { - final Ansi ansi = Ansi.ansi(); - for (final String name : names) { - final Code[] codes = styleMap.get(name); - if (codes != null) { - render(ansi, codes); + private void render(final String input, final StringBuilder output, final String... styleNames) { + boolean first = true; + for (final String styleName : styleNames) { + final String escape = styleMap.get(toRootUpperCase(styleName)); + if (escape != null) { + merge(escape, output, first); } else { - render(ansi, toCode(name)); + merge(AnsiEscape.createSequence(styleName), output, first); } + first = false; + } + output.append(input).append(AnsiEscape.getDefaultStyle()); + } + + private static void merge(final String escapeSequence, final StringBuilder output, final boolean first) { + if (first) { + output.append(escapeSequence); + } else { + // Delete the trailing AnsiEscape.SUFFIX + output.setLength(output.length() - 1); + output.append(AnsiEscape.SEPARATOR.getCode()); + output.append(escapeSequence.substring(CSI_LENGTH)); } - return ansi.a(text).reset().toString(); } // EXACT COPY OF StringBuilder version of the method but typed as String for input @Override public void render(final String input, final StringBuilder output, final String styleName) throws IllegalArgumentException { - output.append(render(input, styleName)); + render(input, output, styleName.split(",", -1)); } @Override public void render(final StringBuilder input, final StringBuilder output) throws IllegalArgumentException { - int i = 0; - int j, k; + int pos = 0; + int beginTokenPos, endTokenPos; while (true) { - j = input.indexOf(beginToken, i); - if (j == -1) { - if (i == 0) { - output.append(input); - return; - } - output.append(input.substring(i, input.length())); + beginTokenPos = input.indexOf(beginToken, pos); + if (beginTokenPos == -1) { + output.append(pos == 0 ? input : input.substring(pos, input.length())); return; } - output.append(input.substring(i, j)); - k = input.indexOf(endToken, j); + output.append(input.substring(pos, beginTokenPos)); + endTokenPos = input.indexOf(endToken, beginTokenPos); - if (k == -1) { - output.append(input); + if (endTokenPos == -1) { + LOGGER.warn( + "Missing matching end token {} for token at position {}: '{}'", endToken, beginTokenPos, input); + output.append(beginTokenPos == 0 ? input : input.substring(beginTokenPos, input.length())); return; } - j += beginTokenLen; - final String spec = input.substring(j, k); + beginTokenPos += beginTokenLen; + final String spec = input.substring(beginTokenPos, endTokenPos); - final String[] items = spec.split(AnsiRenderer.CODE_TEXT_SEPARATOR, 2); + final String[] items = spec.split("\\s", 2); if (items.length == 1) { - output.append(input); - return; + LOGGER.warn("Missing argument in ANSI escape specification '{}'", spec); + output.append(beginToken).append(spec).append(endToken); + } else { + render(items[1], output, items[0].split(",", -1)); } - final String replacement = render(items[1], items[0].split(",")); - - output.append(replacement); - - i = k + endTokenLen; + pos = endTokenPos + endTokenLen; } } - private Code toCode(final String name) { - return Code.valueOf(toRootUpperCase(name)); + public Map<String, String> getStyleMap() { + return styleMap; } @Override public String toString() { - return "JAnsiMessageRenderer [beginToken=" + beginToken + ", beginTokenLen=" + beginTokenLen + ", endToken=" + return "AnsiMessageRenderer [beginToken=" + beginToken + ", beginTokenLen=" + beginTokenLen + ", endToken=" + endToken + ", endTokenLen=" + endTokenLen + ", styleMap=" + styleMap + "]"; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java index 44016664af..dd7df75faf 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java @@ -23,10 +23,8 @@ import java.util.List; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MultiformatMessage; -import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilderFormattable; @@ -52,12 +50,7 @@ public class MessagePatternConverter extends LogEventPatternConverter { for (final String option : options) { switch (toRootUpperCase(option)) { case "ANSI": - if (Loader.isJansiAvailable()) { - return new JAnsiTextRenderer(options, JAnsiTextRenderer.DefaultMessageStyleMap); - } - StatusLogger.getLogger() - .warn("You requested ANSI message rendering but JANSI is not on the classpath."); - return null; + return new JAnsiTextRenderer(options, JAnsiTextRenderer.DEFAULT_MESSAGE_STYLE_MAP); case "HTML": return new HtmlTextRenderer(options); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java index 64ee6be359..4ae5d46335 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java @@ -349,6 +349,10 @@ public final class Loader { } } + /** + * @deprecated Since 2.25.0 without a replacement. + */ + @Deprecated public static boolean isJansiAvailable() { return isClassAvailable("org.fusesource.jansi.AnsiRenderer"); } diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index cb81b04b2c..6f3850eb58 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -100,7 +100,6 @@ <javax-persistence.version>2.2</javax-persistence.version> <javax-servlet.version>4.0.1</javax-servlet.version> <javax-servlet-jsp.version>2.3.3</javax-servlet-jsp.version> - <jansi.version>2.4.1</jansi.version> <java-allocation-instrumenter.version>3.3.4</java-allocation-instrumenter.version> <jazzer.version>0.22.1</jazzer.version> <jconsole.version>1.7.0</jconsole.version> @@ -471,12 +470,6 @@ <version>${jakarta-mail.version}</version> </dependency> - <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <version>${jansi.version}</version> - </dependency> - <!-- Used for garbage-free tests: --> <dependency> <groupId>com.google.code.java-allocation-instrumenter</groupId> diff --git a/pom.xml b/pom.xml index 03760b9eb0..88bf7db62f 100644 --- a/pom.xml +++ b/pom.xml @@ -345,7 +345,6 @@ <site-disruptor.version>4.0.0</site-disruptor.version> <site-flume.version>1.11.0</site-flume.version> <site-jackson.version>2.18.0</site-jackson.version> - <site-jansi.version>1.18</site-jansi.version> <site-javax-mail.version>1.6.2</site-javax-mail.version> <site-jctools.version>4.0.5</site-jctools.version> <site-je.version>18.3.12</site-je.version> @@ -769,12 +768,6 @@ <type>pom</type> </dependency> - <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <version>${site-jansi.version}</version> - </dependency> - <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> diff --git a/src/changelog/.2.x.x/1736_split_jansi_support.xml b/src/changelog/.2.x.x/1736_split_jansi_support.xml new file mode 100644 index 0000000000..a585fed74f --- /dev/null +++ b/src/changelog/.2.x.x/1736_split_jansi_support.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://logging.apache.org/xml/ns" + xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="changed"> + <issue id="1736" link="https://github.com/apache/logging-log4j2/issues/1736"/> + <description format="asciidoc">Remove JAnsi library support. Windows 10 console has supported ANSI escapes since 2017.</description> +</entry> diff --git a/src/changelog/.2.x.x/2916_rewrite_jansi_renderer.xml b/src/changelog/.2.x.x/2916_rewrite_jansi_renderer.xml new file mode 100644 index 0000000000..7fad82b6d5 --- /dev/null +++ b/src/changelog/.2.x.x/2916_rewrite_jansi_renderer.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://logging.apache.org/xml/ns" + xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="changed"> + <issue id="2916" link="https://github.com/apache/logging-log4j2/pull/2916"/> + <description format="asciidoc">Rewrite `JAnsiTextRenderer` to work without JAnsi library.</description> +</entry> diff --git a/src/site/antora/antora.tmpl.yml b/src/site/antora/antora.tmpl.yml index 8ec9a597b2..7cdcf41ecf 100644 --- a/src/site/antora/antora.tmpl.yml +++ b/src/site/antora/antora.tmpl.yml @@ -59,7 +59,6 @@ asciidoc: disruptor-version: "${site-disruptor.version}" flume-version: "${site-flume.version}" jackson-version: "${site-jackson.version}" - jansi-version: "${site-jansi.version}" javax-mail-version: "${site-javax-mail.version}" jctools-version: "${site-jctools.version}" je-version: "${site-je.version}" diff --git a/src/site/antora/antora.yml b/src/site/antora/antora.yml index 21652d7e98..ff6edf566a 100644 --- a/src/site/antora/antora.yml +++ b/src/site/antora/antora.yml @@ -59,7 +59,6 @@ asciidoc: disruptor-version: "1.2.3-disruptor" flume-version: "1.2.3-flume" jackson-version: "1.2.3-jackson" - jansi-version: "1.2.3-jansi" javax-mail-version: "1.2.3-javax-mail" jctools-version: "1.2.3-jctools" je-version: "1.2.3-je" diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders.adoc index 6f0c4aaad7..a43675fb54 100644 --- a/src/site/antora/modules/ROOT/pages/manual/appenders.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/appenders.adoc @@ -216,7 +216,7 @@ They are documented in separate pages based on their target resource: === Console Appender As one might expect, the Console Appender writes its output to either the standard output or standard error output. -The appender supports four different ways to access the output streams: +The appender supports three different ways to access the output streams: `direct`:: This mode gives the best performance. @@ -236,39 +236,6 @@ This setting might be useful in multi-application environments. Some application servers modify `System.out` and `System.err` to always point to the currently running application. ==== -`JANSI`:: -If the application is running on Windows and the -https://fusesource.github.io/jansi/[JANSI library] -is available, the Console appender will use JANSI to emulate ANSI sequence support. -This mode can be disabled by setting the -xref:manual/systemproperties.adoc#log4j2.skipJansi[`log4j2.skipJansi`] -configuration attribute to `true`. -+ -Additional runtime dependencies are required to use JANSI: -+ -[tabs] -==== -Maven:: -+ -[source,xml,subs="+attributes"] ----- -<dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <version>{jansi-version}</version> -</dependency> - ----- - -Gradle:: -+ -[source,groovy,subs="+attributes"] ----- -runtimeOnly 'org.fusesource.jansi:jansi:{jansi-version}' ----- - -==== - [#ConsoleAppender-attributes] .Console Appender configuration attributes [cols="1m,1,1,5"] @@ -311,9 +278,7 @@ If other logging backends or the application itself uses `System.out/System.err` ==== This setting is incompatible with the -<<ConsoleAppender-attr-follow,`follow` attribute>> -and -xref:manual/systemproperties.adoc#log4j2.skipJansi[JANSI support]. +<<ConsoleAppender-attr-follow,`follow` attribute>>. | [[ConsoleAppender-attr-follow]] follow @@ -328,9 +293,7 @@ https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#setOut-java.io.P Otherwise, the value of `System.out` (resp. `System.err`) at configuration time will be used. This setting is incompatible with the -<<ConsoleAppender-attr-direct,`direct` attribute>> -and -xref:manual/systemproperties.adoc#log4j2.skipJansi[JANSI support]. +<<ConsoleAppender-attr-direct,`direct` attribute>>. | [[ConsoleAppender-attr-ignoreExceptions]] ignoreExceptions diff --git a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc index 4734a3e39c..5dc7a852c1 100644 --- a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc @@ -209,7 +209,7 @@ The optional footer to include at the bottom of each log file |Default value |`false` |=== -If `true`, do not output ANSI escape codes +If `true`, do not output ANSI escape codes. [#plugin-attr-noConsoleNoAnsi] ==== `noConsoleNoAnsi` @@ -220,7 +220,7 @@ If `true`, do not output ANSI escape codes |Default value |`false` |=== -If `true` and `System.console()` is null, do not output ANSI escape codes +If `true` and `System.console()` is `null`, do not output ANSI escape codes [#plugin-elements] === Plugin elements @@ -1584,19 +1584,14 @@ If your terminal supports 24-bit colors, you can specify: [#jansi] ==== ANSI styling on Windows -ANSI escape sequences are supported natively on many platforms, but not by default on Windows. -To enable ANSI support add the -http://fusesource.github.io/jansi/[Jansi] -dependency to your application, and set xref:manual/systemproperties.adoc#log4j2.skipJansi[the `log4j2.skipJansi` system property] to `false`. -This allows Log4j to use Jansi to add ANSI escape codes when writing to the console. +ANSI escape sequences are supported natively on many platforms, but are disabled by default in `cmd.exe` on Windows. +To enable ANSI escape sequences, create a registry key named `HKEY_CURRENT_USER\Console\VirtualTerminalLevel` of type `DWORD` and set its value to `0x1`. -[NOTE] -==== -Before Log4j 2.10, Jansi was enabled by default. -The fact that Jansi requires native code means that Jansi can only be loaded by a single class loader. -For web applications, this means the Jansi jar has to be in the web container's classpath. -To avoid causing problems for web applications, Log4j no longer automatically tries to load Jansi without explicit configuration from Log4j 2.10 onward. -==== +See +https://devblogs.microsoft.com/commandline/understanding-windows-console-host-settings/[Understanding Windows Console Host Settings] +and +https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences[Console Virtual Terminal Sequences] +Microsoft documentation for more details. [#garbage-free] === Garbage-free configuration diff --git a/src/site/antora/modules/ROOT/pages/manual/systemproperties.adoc b/src/site/antora/modules/ROOT/pages/manual/systemproperties.adoc index e72136de40..09d963d5d5 100644 --- a/src/site/antora/modules/ROOT/pages/manual/systemproperties.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/systemproperties.adoc @@ -149,13 +149,6 @@ include::partial$manual/systemproperties/properties-configuration-factory.adoc[l include::partial$manual/systemproperties/properties-garbage-collection.adoc[leveloffset=+2] -[id=properties-jansi] -=== JANSI - -If the https://fusesource.github.io/jansi/[JANSI] library is on the runtime classpath of the application, the following property can be used to control its usage: - -include::partial$manual/systemproperties/properties-jansi.adoc[leveloffset=+2] - [id=properties-jmx] === JMX diff --git a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc deleted file mode 100644 index 6b58ac8c58..0000000000 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc +++ /dev/null @@ -1,32 +0,0 @@ -//// - 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. -//// -[id=log4j2.skipJansi] -== `log4j2.skipJansi` - -[cols="1h,5"] -|=== -| Env. variable | `LOG4J_SKIP_JANSI` -| Type | `boolean` -| Default value | `true` -|=== - -If the following conditions are satisfied: - -* Log4j runs on Windows, -* this property is set to `false`, - -Log4j will use the JANSI library to color the output of the console appender. \ No newline at end of file
