From 982e0bc1f6f0fabac5722c1e56f1fd40046bf0f6 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 2 Sep 2024 17:52:25 +0200 Subject: [PATCH 1/7] Split and modernize JANSI support. This commit: - Adds a new `ConsoleStreamProvider` plugin type that can decide how the Console Appender stream is built. - Moves JAnsi support to a new `log4j-jansi` module. - Upgrades the JAnsi library to version 2.x Fixes #1736. --- log4j-core/pom.xml | 16 +- .../log4j/core/appender/ConsoleAppender.java | 202 +++++------------- .../DefaultConsoleStreamSupplier.java | 139 ++++++++++++ .../log4j/core/impl/CoreProperties.java | 6 - .../log4j/core/layout/PatternLayout.java | 17 +- .../.log4j-plugin-processing-activator | 1 + log4j-jansi/pom.xml | 92 ++++++++ .../jansi/JansiConsoleStreamSupplier.java | 56 +++++ .../logging/log4j/jansi/JansiProperties.java | 27 +++ .../jansi/JansiConsoleStreamSupplierTest.java | 70 ++++++ .../JansiConsoleStreamSupplierTest.xml | 34 +++ pom.xml | 7 + .../.3.x.x/1736_split_jansi_support.xml | 8 + .../antora/modules/ROOT/pages/components.adoc | 18 ++ .../modules/ROOT/pages/manual/appenders.adoc | 52 ++--- .../ROOT/pages/manual/pattern-layout.adoc | 8 +- .../ROOT/partials/components/log4j-jansi.adoc | 41 ++++ .../systemproperties/properties-jansi.adoc | 15 +- 18 files changed, 587 insertions(+), 222 deletions(-) create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java create mode 100644 log4j-jansi/.log4j-plugin-processing-activator create mode 100644 log4j-jansi/pom.xml create mode 100644 log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java create mode 100644 log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java create mode 100644 log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java create mode 100644 log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml create mode 100644 src/changelog/.3.x.x/1736_split_jansi_support.xml create mode 100644 src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml index c007ffc6c36..5b9f5152767 100644 --- a/log4j-core/pom.xml +++ b/log4j-core/pom.xml @@ -45,12 +45,9 @@ org.jspecify.*;resolution:=optional, com.conversantmedia.util.concurrent;resolution:=optional; - com.lmax.disruptor.*;resolution:=optional, org.apache.commons.compress.*;resolution:=optional, - org.fusesource.jansi;resolution:=optional, - org.jspecify.annotations.*;resolution:=optional, - + java.lang.management;resolution:=optional, javax.management.*;resolution:=optional, @@ -65,12 +62,10 @@ com.conversantmedia.disruptor;transitive=false, - com.lmax.disruptor;transitive=false, java.management;transitive=false, java.sql;transitive=false, java.xml;transitive=false, - jdk.unsupported;transitive=false, - org.fusesource.jansi;transitive=false, + jdk.unsupported;transitive=false ${log4j.docgen.pluginDescriptorsDir.phase1} @@ -119,12 +114,7 @@ disruptor true - - - org.fusesource.jansi - jansi - true - + 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 5077bba5ed7..5d9bddcc4fa 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 @@ -16,33 +16,27 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.OutputStream; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; +import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.impl.CoreProperties.ConsoleProperties; 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.kit.env.PropertyEnvironment; import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.plugins.validation.constraints.Required; -import org.apache.logging.log4j.util.Chars; -import org.apache.logging.log4j.util.PropertiesUtil; +import org.jspecify.annotations.Nullable; /** * Appends log events to System.out or System.err using a layout specified by the user. The @@ -127,7 +121,7 @@ public static ConsoleAppender createDefaultAppenderForLayout(final Layout layout "DefaultConsole-" + COUNT.incrementAndGet(), layout, null, - getDefaultManager(DEFAULT_TARGET, false, false, layout), + getDefaultManager(layout), true, DEFAULT_TARGET, null); @@ -172,157 +166,40 @@ public B setDirect(final boolean shouldDirect) { @Override public ConsoleAppender build() { - if (follow && direct) { - throw new IllegalArgumentException( - "Cannot use both follow and direct on ConsoleAppender '" + getName() + "'"); - } final Layout layout = getOrCreateLayout(target.getDefaultCharset()); + final Configuration configuration = getConfiguration(); - final PropertyEnvironment propertyEnvironment = - configuration != null && configuration.getLoggerContext() != null - ? configuration.getLoggerContext().getEnvironment() - : PropertyEnvironment.getGlobal(); + final PropertyEnvironment propertyEnvironment = configuration.getLoggerContext() != null + ? configuration.getLoggerContext().getEnvironment() + : PropertyEnvironment.getGlobal(); + + final List suppliers = + configuration.getComponent(new @Namespace(ConsoleStreamSupplier.NAMESPACE) Key<>() {}); + final OutputStream stream = suppliers.stream() + .map(s -> s.getOutputStream(follow, direct, target, propertyEnvironment)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + if (stream == null) { + LOGGER.warn("No output stream found for target {}", 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, propertyEnvironment), - 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 layout) { - final OutputStream os = getOutputStream(follow, direct, target, PropertyEnvironment.getGlobal()); - + private static OutputStreamManager getDefaultManager(final Layout layout) { + final OutputStream os = new CloseShieldOutputStream( + ConsoleAppender.DEFAULT_TARGET == Target.SYSTEM_ERR ? System.err : System.out); // 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 layout, - final PropertyEnvironment properties) { - final OutputStream os = getOutputStream(follow, direct, target, properties); - final String managerName = target.name() + '.' + follow + '.' + direct; - return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); - } - - private static OutputStream getOutputStream( - final boolean follow, final boolean direct, final Target target, final PropertyEnvironment properties) { - 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); - } - if (!PropertiesUtil.getProperties().isOsWindows() - || !Boolean.FALSE.equals( - properties.getProperty(ConsoleProperties.class).jansiEnabled()) - || 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 String clean(final String string) { - return string.replace(Chars.NUL, Chars.SPACE); - } - - /** - * An implementation of OutputStream that redirects to the current System.err. - */ - private static class SystemErrStream extends OutputStream { - public SystemErrStream() {} - - @Override - public void close() { - // do not close sys err! - } - - @Override - public void flush() { - System.err.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - System.err.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) throws IOException { - System.err.write(b, off, len); - } - - @Override - public void write(final int b) { - System.err.write(b); - } - } - - /** - * An implementation of OutputStream that redirects to the current System.out. - */ - private static class SystemOutStream extends OutputStream { - public SystemOutStream() {} - - @Override - public void close() { - // do not close sys out! - } - - @Override - public void flush() { - System.out.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - System.out.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) throws IOException { - System.out.write(b, off, len); - } - - @Override - public void write(final int b) throws IOException { - System.out.write(b); - } - } - /** * Data to pass to factory method.Unable to instantiate */ @@ -366,4 +243,23 @@ public OutputStreamManager createManager(final String name, final FactoryData da public Target getTarget() { return target; } + + /** + * Abstracts the various ways `System.out` can be accessed. + * + * @since 3.0.0 + */ + public interface ConsoleStreamSupplier { + + /** + * The Log4j plugin namespace of plugins implementing this interface. + */ + String NAMESPACE = "Console"; + + /** + * @return Selects the output stream to use or {@code null} in case of error. + */ + @Nullable + OutputStream getOutputStream(boolean follow, boolean direct, Target target, PropertyEnvironment properties); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java new file mode 100644 index 00000000000..858233c0f80 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java @@ -0,0 +1,139 @@ +/* + * 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.internal; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.util.CloseShieldOutputStream; +import org.apache.logging.log4j.kit.env.PropertyEnvironment; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Ordered; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@Plugin +@Namespace(ConsoleAppender.ConsoleStreamSupplier.NAMESPACE) +@Ordered(Ordered.LAST) +@NullMarked +public class DefaultConsoleStreamSupplier implements ConsoleAppender.ConsoleStreamSupplier { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + @Override + @Nullable + public OutputStream getOutputStream( + boolean follow, boolean direct, ConsoleAppender.Target target, PropertyEnvironment properties) { + if (follow && direct) { + LOGGER.error("Cannot use both `follow` and `direct` on ConsoleAppender."); + return null; + } + return switch (target) { + case SYSTEM_ERR -> getErr(follow, direct); + case SYSTEM_OUT -> getOut(follow, direct); + }; + } + + private OutputStream getErr(final boolean follow, final boolean direct) { + if (direct) { + return new CloseShieldOutputStream(new FileOutputStream(FileDescriptor.err)); + } + if (follow) { + return new SystemErrStream(); + } + return new CloseShieldOutputStream(System.err); + } + + private OutputStream getOut(final boolean follow, final boolean direct) { + if (direct) { + return new CloseShieldOutputStream(new FileOutputStream(FileDescriptor.out)); + } + if (follow) { + return new SystemOutStream(); + } + return new CloseShieldOutputStream(System.out); + } + + /** + * An implementation of OutputStream that redirects to the current System.err. + */ + private static class SystemErrStream extends OutputStream { + public SystemErrStream() {} + + @Override + public void close() { + // do not close sys err! + } + + @Override + public void flush() { + System.err.flush(); + } + + @Override + public void write(final byte[] b) throws IOException { + System.err.write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) { + System.err.write(b, off, len); + } + + @Override + public void write(final int b) { + System.err.write(b); + } + } + + /** + * An implementation of OutputStream that redirects to the current System.out. + */ + private static class SystemOutStream extends OutputStream { + public SystemOutStream() {} + + @Override + public void close() { + // do not close sys out! + } + + @Override + public void flush() { + System.out.flush(); + } + + @Override + public void write(final byte[] b) throws IOException { + System.out.write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) { + System.out.write(b, off, len); + } + + @Override + public void write(final int b) { + System.out.write(b); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreProperties.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreProperties.java index 27188eba778..82abc8733a1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreProperties.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreProperties.java @@ -107,12 +107,6 @@ public record ConfigurationProperties( boolean usePreciseClock, @Log4jProperty(defaultValue = "5000") long waitMillisBeforeStopOldConfig) {} - /** - * Properties to tune console output. - */ - @Log4jProperty(name = "console") - public record ConsoleProperties(@Nullable Boolean jansiEnabled) {} - /** * Properties to tune garbage collection. */ 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 24ce2d345a5..a0bb6b0a146 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 @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.layout; -import static java.lang.Boolean.TRUE; - import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; @@ -27,20 +25,17 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.impl.CoreProperties; import org.apache.logging.log4j.core.pattern.FormattingInfo; 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.kit.env.PropertyEnvironment; import org.apache.logging.log4j.kit.recycler.Recycler; import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; import org.apache.logging.log4j.plugins.PluginFactory; -import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.Strings; /** @@ -577,7 +572,7 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui private boolean alwaysWriteExceptions = true; @PluginBuilderAttribute - private boolean disableAnsi = !useAnsiEscapeCodes(); + private boolean disableAnsi; @PluginBuilderAttribute private boolean noConsoleNoAnsi; @@ -590,16 +585,6 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui private Builder() {} - private boolean useAnsiEscapeCodes() { - final PropertyEnvironment properties = PropertyEnvironment.getGlobal(); - final boolean isPlatformSupportsAnsi = - !PropertiesUtil.getProperties().isOsWindows(); - final boolean isJansiRequested = TRUE.equals(PropertyEnvironment.getGlobal() - .getProperty(CoreProperties.ConsoleProperties.class) - .jansiEnabled()); - return isPlatformSupportsAnsi || isJansiRequested; - } - /** * @param pattern * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. diff --git a/log4j-jansi/.log4j-plugin-processing-activator b/log4j-jansi/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-jansi/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-jansi/pom.xml b/log4j-jansi/pom.xml new file mode 100644 index 00000000000..1a86af470c8 --- /dev/null +++ b/log4j-jansi/pom.xml @@ -0,0 +1,92 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j + ${revision} + ../log4j-parent + + + log4j-jansi + Apache Log4j Core: JANSI support + Support for JANSI library for Log4j Core. + + + + + org.apache.logging.log4j + log4j-core + + + + org.apache.logging.log4j + log4j-kit + + + + org.apache.logging.log4j + log4j-plugins + + + + org.fusesource.jansi + jansi + + + + org.assertj + assertj-core + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.apache.logging.log4j + log4j-core-test + test + + + org.hamcrest + hamcrest + + + junit + junit + + + org.junit.vintage + junit-vintage-engine + + + + + + diff --git a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java new file mode 100644 index 00000000000..c50d6e67a28 --- /dev/null +++ b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java @@ -0,0 +1,56 @@ +/* + * 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.jansi; + +import java.io.OutputStream; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.util.CloseShieldOutputStream; +import org.apache.logging.log4j.kit.env.PropertyEnvironment; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Ordered; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.fusesource.jansi.AnsiConsole; +import org.jspecify.annotations.Nullable; + +/** + * Uses the JAnsi library to provide standard output and error. + */ +@Plugin +@Namespace(ConsoleAppender.ConsoleStreamSupplier.NAMESPACE) +@Ordered(0) +public class JansiConsoleStreamSupplier implements ConsoleAppender.ConsoleStreamSupplier { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + @Override + public @Nullable OutputStream getOutputStream( + boolean follow, boolean direct, ConsoleAppender.Target target, PropertyEnvironment properties) { + if (properties.getProperty(JansiProperties.class).jansiEnabled()) { + if (follow || direct) { + LOGGER.error( + "Can not use neither `follow` nor `direct` on ConsoleAppender, since JAnsi library is used."); + return null; + } + return new CloseShieldOutputStream( + target == ConsoleAppender.Target.SYSTEM_ERR ? AnsiConsole.err() : AnsiConsole.out()); + } + LOGGER.debug("Ignoring JAnsi library since configuration property `log4j.console.jansiEnabled` is false."); + return null; + } +} diff --git a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java new file mode 100644 index 00000000000..8a48b1d8a55 --- /dev/null +++ b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java @@ -0,0 +1,27 @@ +/* + * 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.jansi; + +import org.apache.logging.log4j.kit.env.Log4jProperty; + +/** + * Properties to tune console output. + * + * @param jansiEnabled If {@code true}, the JAnsi library will be used whenever available. + */ +@Log4jProperty(name = "console") +public record JansiProperties(@Log4jProperty(defaultValue = "true") boolean jansiEnabled) {} diff --git a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java new file mode 100644 index 00000000000..0aae5dc50bf --- /dev/null +++ b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java @@ -0,0 +1,70 @@ +/* + * 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.jansi; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.OutputStreamManager; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.CloseShieldOutputStream; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.fusesource.jansi.AnsiConsole; +import org.junit.jupiter.api.Test; + +public class JansiConsoleStreamSupplierTest { + + @Test + @LoggerContextSource("JansiConsoleStreamSupplierTest.xml") + void usesJansiByDefault(final @Named("CONSOLE") ConsoleAppender appender) { + assumeThat(AnsiConsole.out()).isNotEqualTo(System.out); + + final OutputStreamManager manager = appender.getManager(); + assertOutputStreamIsEqual(AnsiConsole.out(), manager); + } + + @Test + @SetTestProperty(key = "log4j.console.jansiEnabled", value = "false") + @LoggerContextSource("JansiConsoleStreamSupplierTest.xml") + void whenJansiDisabled_usesSystemOut(final @Named("CONSOLE") ConsoleAppender appender) { + assumeThat(AnsiConsole.out()).isNotEqualTo(System.out); + + final OutputStreamManager manager = appender.getManager(); + assertOutputStreamIsEqual(System.out, manager); + } + + private static void assertOutputStreamIsEqual(final OutputStream expected, final OutputStreamManager manager) { + final OutputStream wrappedActual = assertDoesNotThrow(() -> { + final Method getOutputStream = OutputStreamManager.class.getDeclaredMethod("getOutputStream"); + getOutputStream.setAccessible(true); + return (OutputStream) getOutputStream.invoke(manager); + }); + assertThat(wrappedActual).isInstanceOf(CloseShieldOutputStream.class); + final OutputStream actual = assertDoesNotThrow(() -> { + final Field delegate = CloseShieldOutputStream.class.getDeclaredField("delegate"); + delegate.setAccessible(true); + return (OutputStream) delegate.get(wrappedActual); + }); + assertThat(actual).isEqualTo(expected); + } +} diff --git a/log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml b/log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml new file mode 100644 index 00000000000..4c33f668edb --- /dev/null +++ b/log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 1931478e9fb..cfdfbee5449 100644 --- a/pom.xml +++ b/pom.xml @@ -241,6 +241,7 @@ log4j-csv log4j-docker log4j-gc-test + log4j-jansi log4j-jctools log4j-jdbc log4j-jdbc-dbcp2 @@ -436,6 +437,12 @@ ${log4j-api.version} + + org.apache.logging.log4j + log4j-jansi + ${project.version} + + org.apache.logging.log4j log4j-jctools diff --git a/src/changelog/.3.x.x/1736_split_jansi_support.xml b/src/changelog/.3.x.x/1736_split_jansi_support.xml new file mode 100644 index 00000000000..2220a452047 --- /dev/null +++ b/src/changelog/.3.x.x/1736_split_jansi_support.xml @@ -0,0 +1,8 @@ + + + + Move JAnsi support to a new `log4j-jansi` module and upgrade to version 2.x of the library. + diff --git a/src/site/antora/modules/ROOT/pages/components.adoc b/src/site/antora/modules/ROOT/pages/components.adoc index b354b6b91f6..932d17a2d5b 100644 --- a/src/site/antora/modules/ROOT/pages/components.adoc +++ b/src/site/antora/modules/ROOT/pages/components.adoc @@ -182,6 +182,24 @@ for more information. include::partial$components/log4j-flume-ng.adoc[] +[#log4j-jansi] +== `log4j-jansi` + +|=== +| JPMS module +| `org.apache.logging.log4j.jansi` +|=== + +The `log4j-jansi` artifact contains an extension for the +xref:manual/appenders.adoc#ConsoleAppender[Console Appender] +that uses the +https://fusesource.github.io/jansi/[JAnsi library] +to handle ANSI escapes. + +See xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[`log4j.console.jansiEnabled`] for more details. + +include::partial$components/log4j-jansi.adoc[] + [#log4j-jctools] == `log4j-jctools` diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders.adoc index a25db96530b..28fa1dd8c9b 100644 --- a/src/site/antora/modules/ROOT/pages/manual/appenders.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/appenders.adoc @@ -234,38 +234,38 @@ 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. ==== +[#ConsoleAppender-jansi] `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 enabled by setting the -xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[`log4j.console.jansiEnabled`] -configuration attribute to `true`. +If the +xref:components.adoc#log4j-jansi[`log4j-jansi` extension] +is available, the Console appender will use the +https://fusesource.github.io/jansi/[JAnsi library] +to handle ANSI escapes. +The library: + -Additional runtime dependencies are required to use JANSI: +-- +* Implement ANSI escape handling on old Windows consoles. +* Strips ANSI escape codes if process output is being redirected and not attached to a terminal +-- + -[tabs] +See https://github.com/fusesource/jansi?tab=readme-ov-file#features[Features of JAnsi] for more details. ++ +[TIP] +==== +Support for ANSI escape sequences was added to Windows 10 console around 2017. +See +https://superuser.com/questions/413073/windows-console-with-ansi-colors-handling[Windows console with ANSI colors handling] +for more details. ==== -Maven:: + -[source,xml,subs="+attributes"] ----- - - org.fusesource.jansi - jansi - {jansi-version} - - ----- - -Gradle:: +The `JANSI` mode can be disabled by setting the +xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[`log4j.console.jansiEnabled`] +configuration attribute to `false`. +Additional runtime dependencies are required to use JAnsi: + -[source,groovy,subs="+attributes"] ----- -runtimeOnly 'org.fusesource.jansi:jansi:{jansi-version}' ----- - -==== +-- +include::partial$components/log4j-jansi.adoc[] +-- [#ConsoleAppender-attributes] .Console Appender configuration attributes 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 9e9b5055027..6c8bf5b9d05 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,13 @@ 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. + +[NOTE] +==== +If you use a Console Appender in xref:manual/appenders.adoc#ConsoleAppender-jansi[`JANSI` mode], you should keep this setting at its default `false` value. +The JAnsi library will take care of stripping ANSI Escape if the output of the console is not a terminal. +==== [#plugin-attr-noConsoleNoAnsi] ==== `noConsoleNoAnsi` diff --git a/src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc b/src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc new file mode 100644 index 00000000000..d7864aff767 --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc @@ -0,0 +1,41 @@ +//// + 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. +//// + +[tabs] +==== +Maven:: ++ +We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. ++ +[source,xml] +---- + + org.apache.logging.log4j + log4j-jansi + runtime + +---- + +Gradle:: ++ +We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. ++ +[source,groovy] +---- +runtimeOnly 'org.apache.logging.log4j:log4j-jansi' +---- +==== \ No newline at end of file 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 index 0aefd398320..ca43e6bc296 100644 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc @@ -21,12 +21,13 @@ |=== | Env. variable | `LOG4J_CONSOLE_JANSI_ENABLED` | Type | `boolean` -| Default value | `false` +| Default value | `true` |=== -If the following conditions are satisfied: - -* Log4j runs on Windows, -* this property is set to `true`, - -Log4j will use the JANSI library to color the output of the console appender. \ No newline at end of file +If the +xref:components.adoc#log4j-jansi[`log4j-jansi`] +extension is available and this value is `true`, the +xref:manual/appenders.adoc#ConsoleAppender[Console Appender] +will use the +https://fusesource.github.io/jansi/[JAnsi library] +to handle ANSI escapes. From f77433528bab2d08b4566ba4ae91f658ab98f9db Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 3 Sep 2024 09:48:11 +0200 Subject: [PATCH 2/7] Replace JANSI with JLine 3 JANSI is deprecated, see fusesource/jansi#294 for details. --- log4j-jansi/pom.xml | 18 +++++++++++++-- .../logging/log4j/jansi/package-info.java | 22 +++++++++++++++++++ log4j-parent/pom.xml | 7 ------ 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java diff --git a/log4j-jansi/pom.xml b/log4j-jansi/pom.xml index 1a86af470c8..3b6f0c8454c 100644 --- a/log4j-jansi/pom.xml +++ b/log4j-jansi/pom.xml @@ -28,6 +28,20 @@ Apache Log4j Core: JANSI support Support for JANSI library for Log4j Core. + + 3.26.3 + + + + + + org.jline + jansi-core + ${jline.version} + + + + @@ -46,8 +60,8 @@ - org.fusesource.jansi - jansi + org.jline + jansi-core diff --git a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java new file mode 100644 index 00000000000..cc1d9000c6f --- /dev/null +++ b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +@Export +@Version("3.0.0") +package org.apache.logging.log4j.jansi; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 94b5feee542..515d06f2fe2 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -118,7 +118,6 @@ 2.2.2 2.7.3 2.17.2 - 2.4.0 2.0.1 3.3.4 4.0.5 @@ -416,12 +415,6 @@ ${hsqldb.version} - - org.fusesource.jansi - jansi - ${jansi.version} - - com.google.code.java-allocation-instrumenter From 5ae37df740d450ce457a732ecbd76bbd912d43ea Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 3 Sep 2024 11:39:44 +0200 Subject: [PATCH 3/7] Rewrite `JAnsiTextRenderer` using `AnsiEscape` We rewrite `JAnsiTextRenderer` to use our internal `AnsiEscape` util instead of the external JAnsi library. --- log4j-core-test/pom.xml | 6 - .../core/impl/ThrowableFormatOptionsTest.java | 122 +++--- .../core/pattern/JAnsiTextRendererTest.java | 58 +++ .../core/impl/ThrowableFormatOptions.java | 18 +- .../log4j/core/pattern/AnsiEscape.java | 7 +- .../log4j/core/pattern/JAnsiTextRenderer.java | 366 ++++++++---------- .../core/pattern/MessagePatternConverter.java | 9 +- .../jansi/JansiConsoleStreamSupplier.java | 2 +- .../ConsoleAppenderJAnsiMessageMain.java | 52 +-- .../ConsoleAppenderJAnsiMessageMain.xml | 15 +- .../.3.x.x/2916_rewrite_jansi_renderer.xml | 8 + 11 files changed, 323 insertions(+), 340 deletions(-) create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java rename {log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender => log4j-jansi/src/test/java/org/apache/logging/log4j/jansi}/ConsoleAppenderJAnsiMessageMain.java (52%) rename log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml => log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml (72%) create mode 100644 src/changelog/.3.x.x/2916_rewrite_jansi_renderer.xml diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml index ec83eae4178..69199b6743b 100644 --- a/log4j-core-test/pom.xml +++ b/log4j-core-test/pom.xml @@ -148,12 +148,6 @@ jackson-dataformat-yaml true - - - org.fusesource.jansi - jansi - true - org.junit.jupiter junit-jupiter-engine 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 4719db85637..8d50b4296a9 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,26 @@ */ package org.apache.logging.log4j.core.impl; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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 +71,7 @@ private static ThrowableFormatOptions test( * Test {@code %throwable} with null options. */ @Test - public void testNull() { + void testNull() { test(null, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -79,7 +79,7 @@ public void testNull() { * Test {@code %throwable} */ @Test - public void testEmpty() { + void testEmpty() { test(new String[] {}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -87,7 +87,7 @@ public void testEmpty() { * 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 +95,7 @@ public void testOneNullElement() { * Test {@code %throwable{} } */ @Test - public void testOneEmptyElement() { + void testOneEmptyElement() { test(new String[] {""}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -103,7 +103,7 @@ public void testOneEmptyElement() { * Test {@code %throwable{full} } */ @Test - public void testFull() { + void testFull() { test(new String[] {"full"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -111,7 +111,7 @@ public void testFull() { * 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 +121,7 @@ public void testFullAnsi() { * 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,9 +130,9 @@ public void testFullAnsiEmptyConfig() { private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map styleMap = jansiRenderer.getStyleMap(); + final Map styleMap = jansiRenderer.getStyleMap(); // We have defaults assertFalse(styleMap.isEmpty()); assertNotNull(styleMap.get("Name")); @@ -142,22 +142,22 @@ private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { * 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); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map styleMap = jansiRenderer.getStyleMap(); - assertArrayEquals(new Code[] {Code.RED}, styleMap.get("Warning")); + final Map styleMap = jansiRenderer.getStyleMap(); + assertThat(styleMap.get("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 +165,19 @@ public void testFullAnsiWithCustomStyles() { null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map 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")); + final Map styleMap = jansiRenderer.getStyleMap(); + assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get("Key")).isEqualTo(AnsiEscape.createSequence("BLUE")); + assertThat(styleMap.get("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 +185,19 @@ public void testFullAnsiWithCustomComplexStyles() { null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map 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")); + final Map styleMap = jansiRenderer.getStyleMap(); + assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("BLUE", "BG_RED")); + assertThat(styleMap.get("Warning")).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 +205,7 @@ public void testNone() { * Test {@code %throwable{short} } */ @Test - public void testShort() { + void testShort() { test(new String[] {"short"}, 2, Strings.LINE_SEPARATOR, null); } @@ -213,7 +213,7 @@ public void testShort() { * Test {@code %throwable{10} } */ @Test - public void testDepth() { + void testDepth() { test(new String[] {"10"}, 10, Strings.LINE_SEPARATOR, null); } @@ -221,7 +221,7 @@ public void testDepth() { * Test {@code %throwable{separator(|)} } */ @Test - public void testSeparator() { + void testSeparator() { test(new String[] {"separator(|)"}, Integer.MAX_VALUE, "|", null); } @@ -229,7 +229,7 @@ public void testSeparator() { * Test {@code %throwable{separator()} } */ @Test - public void testSeparatorAsEmpty() { + void testSeparatorAsEmpty() { test(new String[] {"separator()"}, Integer.MAX_VALUE, Strings.EMPTY, null); } @@ -237,7 +237,7 @@ public void testSeparatorAsEmpty() { * Test {@code %throwable{separator(\n)} } */ @Test - public void testSeparatorAsDefaultLineSeparator() { + void testSeparatorAsDefaultLineSeparator() { test( new String[] {"separator(" + Strings.LINE_SEPARATOR + ')'}, Integer.MAX_VALUE, @@ -249,7 +249,7 @@ public void testSeparatorAsDefaultLineSeparator() { * Test {@code %throwable{separator( | )} } */ @Test - public void testSeparatorAsMultipleCharacters() { + void testSeparatorAsMultipleCharacters() { test(new String[] {"separator( | )"}, Integer.MAX_VALUE, " | ", null); } @@ -257,7 +257,7 @@ public void testSeparatorAsMultipleCharacters() { * Test {@code %throwable{full}{separator(|)} } */ @Test - public void testFullAndSeparator() { + void testFullAndSeparator() { test(new String[] {"full", "separator(|)"}, Integer.MAX_VALUE, "|", null); } @@ -265,7 +265,7 @@ public void testFullAndSeparator() { * 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 +277,7 @@ public void testFullAndFiltersAndSeparator() { * Test {@code %throwable{none}{separator(|)} } */ @Test - public void testNoneAndSeparator() { + void testNoneAndSeparator() { test(new String[] {"none", "separator(|)"}, 0, "|", null); } @@ -285,7 +285,7 @@ public void testNoneAndSeparator() { * Test {@code %throwable{short}{separator(|)} } */ @Test - public void testShortAndSeparator() { + void testShortAndSeparator() { test(new String[] {"short", "separator(|)"}, 2, "|", null); } @@ -293,7 +293,7 @@ public void testShortAndSeparator() { * Test {@code %throwable{10}{separator(|)} } */ @Test - public void testDepthAndSeparator() { + void testDepthAndSeparator() { test(new String[] {"10", "separator(|)"}, 10, "|", null); } @@ -301,7 +301,7 @@ public void testDepthAndSeparator() { * Test {@code %throwable{filters(packages)} } */ @Test - public void testFilters() { + void testFilters() { test( new String[] {"filters(packages)"}, Integer.MAX_VALUE, @@ -313,7 +313,7 @@ public void testFilters() { * Test {@code %throwable{filters()} } */ @Test - public void testFiltersAsEmpty() { + void testFiltersAsEmpty() { test(new String[] {"filters()"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); } @@ -321,7 +321,7 @@ public void testFiltersAsEmpty() { * Test {@code %throwable{filters(package1,package2)} } */ @Test - public void testFiltersAsMultiplePackages() { + void testFiltersAsMultiplePackages() { test( new String[] {"filters(package1,package2)"}, Integer.MAX_VALUE, @@ -333,7 +333,7 @@ public void testFiltersAsMultiplePackages() { * Test {@code %throwable{full}{filters(packages)} } */ @Test - public void testFullAndFilters() { + void testFullAndFilters() { test( new String[] {"full", "filters(packages)"}, Integer.MAX_VALUE, @@ -345,7 +345,7 @@ public void testFullAndFilters() { * Test {@code %throwable{none}{filters(packages)} } */ @Test - public void testNoneAndFilters() { + void testNoneAndFilters() { test( new String[] {"none", "filters(packages)"}, 0, @@ -357,7 +357,7 @@ public void testNoneAndFilters() { * Test {@code %throwable{short}{filters(packages)} } */ @Test - public void testShortAndFilters() { + void testShortAndFilters() { test( new String[] {"short", "filters(packages)"}, 2, @@ -369,7 +369,7 @@ public void testShortAndFilters() { * Test {@code %throwable{10}{filters(packages)} } */ @Test - public void testDepthAndFilters() { + void testDepthAndFilters() { test( new String[] {"10", "filters(packages)"}, 10, @@ -381,7 +381,7 @@ public void testDepthAndFilters() { * 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 +393,7 @@ public void testFullAndSeparatorAndFilter() { * 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 +405,7 @@ public void testFullAndSeparatorAndFilters() { * 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 +413,7 @@ public void testNoneAndSeparatorAndFilters() { * Test {@code %throwable{short}{separator(|)}{filters(packages)} } */ @Test - public void testShortAndSeparatorAndFilters() { + void testShortAndSeparatorAndFilters() { test( new String[] {"short", "separator(|)", "filters(packages)"}, 2, @@ -425,7 +425,7 @@ public void testShortAndSeparatorAndFilters() { * 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 +433,7 @@ public void testDepthAndSeparatorAndFilters() { * Test {@code %throwable{full,filters(packages)} } */ @Test - public void testSingleOptionFullAndFilters() { + void testSingleOptionFullAndFilters() { test( new String[] {"full,filters(packages)"}, Integer.MAX_VALUE, @@ -445,7 +445,7 @@ public void testSingleOptionFullAndFilters() { * 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 +453,7 @@ public void testSingleOptionNoneAndFilters() { * Test {@code %throwable{short,filters(packages)} } */ @Test - public void testSingleOptionShortAndFilters() { + void testSingleOptionShortAndFilters() { test( new String[] {"short,filters(packages)"}, 2, @@ -465,7 +465,7 @@ public void testSingleOptionShortAndFilters() { * 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 +473,7 @@ public void testSingleOptionDepthAndFilters() { * 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 +485,7 @@ public void testSingleOptionFullAndMultipleFilters() { * Test {@code %throwable{none,filters(package1,package2)} } */ @Test - public void testSingleOptionNoneAndMultipleFilters() { + void testSingleOptionNoneAndMultipleFilters() { test( new String[] {"none,filters(package1,package2)"}, 0, @@ -497,7 +497,7 @@ public void testSingleOptionNoneAndMultipleFilters() { * Test {@code %throwable{short,filters(package1,package2)} } */ @Test - public void testSingleOptionShortAndMultipleFilters() { + void testSingleOptionShortAndMultipleFilters() { test( new String[] {"short,filters(package1,package2)"}, 2, @@ -509,7 +509,7 @@ public void testSingleOptionShortAndMultipleFilters() { * 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 00000000000..32a07d35f8f --- /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 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/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 6ede9889824..31edd40cf3b 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 @@ -22,9 +22,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.Loader; import org.apache.logging.log4j.core.util.Patterns; -import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; /** @@ -289,17 +287,11 @@ public static ThrowableFormatOptions newInstance(String[] options) { || 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/pattern/AnsiEscape.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java index be655ca0fe4..d51d5e7c824 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 @@ -420,6 +420,11 @@ public static Map createMap(final String values, final String[] * @return a new map */ public static Map createMap(final String[] values, final String[] dontEscapeKeys) { + return createMap(values, dontEscapeKeys, "\\s"); + } + + static Map 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 map = new HashMap<>(); @@ -429,7 +434,7 @@ public static Map createMap(final String[] values, final String[ 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); } } return map; 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 58d4afa46cd..db886aacf1d 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,27 @@ */ 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.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. - * + *

+ *

* The default syntax for embedded ANSI codes is: - * + *

*
  *   @|code(,code)* text|@
  * 
@@ -74,260 +73,209 @@ * * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as * Apache 2.0.) - * - * @see AnsiRenderer */ public final class JAnsiTextRenderer implements TextRenderer { - public static final Map DefaultExceptionStyleMap; - static final Map DefaultMessageStyleMap; - private static final Map> PrefedinedStyleMaps; + private static final Logger LOGGER = StatusLogger.getLogger(); - private static void put(final Map map, final String name, final Code... codes) { - map.put(name, codes); + public static final Map DEFAULT_EXCEPTION_STYLE_MAP; + static final Map DEFAULT_MESSAGE_STYLE_MAP; + private static final Map> PREFEDINED_STYLE_MAPS; + + 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 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 Map.entry(name, sb.toString()); } static { - final Map> tempPreDefs = new HashMap<>(); // Default style: Spock - { - // TODO Should the keys be in an enum? - final Map 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.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 spock = Map.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.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 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.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 temp = new HashMap<>(); - // TODO - DefaultMessageStyleMap = Collections.unmodifiableMap(temp); - } - PrefedinedStyleMaps = Collections.unmodifiableMap(tempPreDefs); + final Map kirk = Map.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.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 = Map.of(); + PREFEDINED_STYLE_MAPS = Map.of("Spock", spock, "Kirk", kirk); } private final String beginToken; private final int beginTokenLen; private final String endToken; private final int endTokenLen; - private final Map styleMap; + private final Map styleMap; - public JAnsiTextRenderer(final String[] formats, final Map defaultStyleMap) { - String tempBeginToken = AnsiRenderer.BEGIN_TOKEN; - String tempEndToken = AnsiRenderer.END_TOKEN; - final Map map; + public JAnsiTextRenderer(final String[] formats, final Map defaultStyleMap) { + final Map map; + // 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); - } 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 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); - } - } - } - } + final String stylesStr = formats[1]; + map = AnsiEscape.createMap( + stylesStr.split("\\s", -1), new String[] {"BeginToken", "EndToken", "Style"}, ","); } else { map = defaultStyleMap; } - styleMap = map; - beginToken = tempBeginToken; - endToken = tempEndToken; - beginTokenLen = tempBeginToken.length(); - endTokenLen = tempEndToken.length(); - } - public Map getStyleMap() { - return styleMap; - } + // Handle the special tokens + beginToken = Objects.toString(map.remove("BeginToken"), BEGIN_TOKEN); + beginTokenLen = beginToken.length(); + endToken = Objects.toString(map.remove("EndToken"), END_TOKEN); + endTokenLen = endToken.length(); + final String predefinedStyle = map.remove("Style"); - private void render(final Ansi ansi, final Code code) { - if (code.isColor()) { - if (code.isBackground()) { - ansi.bg(code.getColor()); + // Create style map + final Map styleMap = new HashMap<>(map.size()); + if (predefinedStyle != null) { + final Map predefinedMap = PREFEDINED_STYLE_MAPS.get(predefinedStyle); + if (predefinedMap != null) { + map.putAll(predefinedMap); } else { - ansi.fg(code.getColor()); + LOGGER.warn( + "Unknown predefined map name {}, pick one of {}", + predefinedStyle, + PREFEDINED_STYLE_MAPS.keySet()); } - } 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); } + styleMap.putAll(map); + this.styleMap = styleMap; } /** - * 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 getStyleMap() { + return styleMap; } @Override 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 1408d4d9e6a..a53a4613656 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 @@ -22,12 +22,10 @@ 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.util.Loader; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MultiformatMessage; import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; -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; @@ -54,12 +52,7 @@ private static TextRenderer loadMessageRenderer(final String[] options) { 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-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java index c50d6e67a28..c00313e3f43 100644 --- a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java +++ b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java @@ -25,7 +25,7 @@ import org.apache.logging.log4j.plugins.Ordered; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.status.StatusLogger; -import org.fusesource.jansi.AnsiConsole; +import org.jline.jansi.AnsiConsole; import org.jspecify.annotations.Nullable; /** diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java similarity index 52% rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java rename to log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java index 56cce35a74b..cbb20094f97 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java +++ b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java @@ -14,22 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.appender; +package org.apache.logging.log4j.jansi; -import static org.fusesource.jansi.Ansi.Color.CYAN; -import static org.fusesource.jansi.Ansi.Color.RED; -import static org.fusesource.jansi.Ansi.ansi; +import static org.jline.jansi.Ansi.Color.CYAN; +import static org.jline.jansi.Ansi.Color.RED; +import static org.jline.jansi.Ansi.ansi; +import java.net.URI; +import java.net.URISyntaxException; 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; +import org.apache.logging.log4j.core.util.NetUtils; /** * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest @@ -39,40 +37,24 @@ *

* *
- * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiMessageMain test
+ * mvn exec:java -Dexec.mainClass=org.apache.logging.log4j.jansi.ConsoleAppenderJAnsiMessageMain
  * 
- * - * or, on Windows: - * - *
- * 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
- * 
- * */ -@Category(Layouts.Jansi.class) public class ConsoleAppenderJAnsiMessageMain { - public static void main(final String[] args) { + public static void main(final String[] args) throws URISyntaxException { 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]; + public void test(final String[] args) throws URISyntaxException { + final URI config = args == null || args.length == 0 + ? ConsoleAppenderJAnsiMessageMain.class + .getResource("/ConsoleAppenderJAnsiMessageMain.xml") + .toURI() + : NetUtils.toURI(args[0]); try (final LoggerContext ctx = - Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { - final Logger logger = LogManager.getLogger(ConsoleAppenderJAnsiMessageMain.class); + Configurator.initialize(getClass().getName(), getClass().getClassLoader(), config)) { + final Logger logger = LogManager.getLogger(); logger.info(ansi().fg(RED).a("Hello").fg(CYAN).a(" World").reset()); // JAnsi format: // logger.info("@|red Hello|@ @|cyan World|@"); diff --git a/log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml b/log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml similarity index 72% rename from log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml rename to log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml index d30b0fb406b..249af27ef7f 100644 --- a/log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml +++ b/log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml @@ -15,16 +15,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - - + + - - - + + diff --git a/src/changelog/.3.x.x/2916_rewrite_jansi_renderer.xml b/src/changelog/.3.x.x/2916_rewrite_jansi_renderer.xml new file mode 100644 index 00000000000..7fad82b6d5b --- /dev/null +++ b/src/changelog/.3.x.x/2916_rewrite_jansi_renderer.xml @@ -0,0 +1,8 @@ + + + + Rewrite `JAnsiTextRenderer` to work without JAnsi library. + From bbd852b1a6bcf9b5fd6969999f3c8dfdd2749628 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 3 Sep 2024 11:47:41 +0200 Subject: [PATCH 4/7] Fix compilation error --- .../logging/log4j/jansi/JansiConsoleStreamSupplierTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java index 0aae5dc50bf..4c8816a182a 100644 --- a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java +++ b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java @@ -29,7 +29,7 @@ import org.apache.logging.log4j.core.util.CloseShieldOutputStream; import org.apache.logging.log4j.plugins.Named; import org.apache.logging.log4j.test.junit.SetTestProperty; -import org.fusesource.jansi.AnsiConsole; +import org.jline.jansi.AnsiConsole; import org.junit.jupiter.api.Test; public class JansiConsoleStreamSupplierTest { From 9319fc6141566179586a479a0effcb6e0d694883 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 3 Sep 2024 12:27:02 +0200 Subject: [PATCH 5/7] Fix test failures --- .../core/impl/ThrowableFormatOptionsTest.java | 18 ++++--- .../log4j/core/appender/ConsoleAppender.java | 18 +++++-- .../log4j/core/pattern/JAnsiTextRenderer.java | 51 ++++++++++--------- 3 files changed, 50 insertions(+), 37 deletions(-) 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 8d50b4296a9..339c1b0e6d2 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,6 +16,7 @@ */ package org.apache.logging.log4j.core.impl; +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; @@ -135,7 +136,7 @@ private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { final Map styleMap = jansiRenderer.getStyleMap(); // We have defaults assertFalse(styleMap.isEmpty()); - assertNotNull(styleMap.get("Name")); + assertNotNull(styleMap.get(toRootUpperCase("Name"))); } /** @@ -150,7 +151,7 @@ void testFullAnsiWithCustomStyle() { assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); - assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); } /** @@ -168,9 +169,9 @@ void testFullAnsiWithCustomStyles() { assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); - assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("RED")); - assertThat(styleMap.get("Key")).isEqualTo(AnsiEscape.createSequence("BLUE")); - assertThat(styleMap.get("Value")).isEqualTo(AnsiEscape.createSequence("CYAN")); + 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")); } /** @@ -188,9 +189,10 @@ void testFullAnsiWithCustomComplexStyles() { assertInstanceOf(JAnsiTextRenderer.class, textRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); - assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("RED")); - assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("BLUE", "BG_RED")); - assertThat(styleMap.get("Warning")).isEqualTo(AnsiEscape.createSequence("CYAN", "BG_BLACK", "UNDERLINE")); + 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")); } /** 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 5d9bddcc4fa..f25c3924bdb 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 @@ -25,6 +25,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.appender.internal.DefaultConsoleStreamSupplier; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.util.CloseShieldOutputStream; @@ -169,12 +170,19 @@ public ConsoleAppender build() { final Layout layout = getOrCreateLayout(target.getDefaultCharset()); final Configuration configuration = getConfiguration(); - final PropertyEnvironment propertyEnvironment = configuration.getLoggerContext() != null - ? configuration.getLoggerContext().getEnvironment() - : PropertyEnvironment.getGlobal(); + final PropertyEnvironment propertyEnvironment; + final List suppliers; + if (configuration != null) { + propertyEnvironment = configuration.getLoggerContext() != null + ? configuration.getLoggerContext().getEnvironment() + : PropertyEnvironment.getGlobal(); + + suppliers = configuration.getComponent(new @Namespace(ConsoleStreamSupplier.NAMESPACE) Key<>() {}); + } else { + propertyEnvironment = PropertyEnvironment.getGlobal(); + suppliers = List.of(new DefaultConsoleStreamSupplier()); + } - final List suppliers = - configuration.getComponent(new @Namespace(ConsoleStreamSupplier.NAMESPACE) Key<>() {}); final OutputStream stream = suppliers.stream() .map(s -> s.getOutputStream(follow, direct, target, propertyEnvironment)) .filter(Objects::nonNull) 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 db886aacf1d..e58a64de21e 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 @@ -23,6 +23,7 @@ import static org.apache.logging.log4j.core.pattern.AnsiEscape.YELLOW; import static org.apache.logging.log4j.util.Strings.toRootUpperCase; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -165,39 +166,41 @@ private static Map.Entry entry(final String name, final AnsiEsca private final Map styleMap; public JAnsiTextRenderer(final String[] formats, final Map defaultStyleMap) { - final Map map; // The format string is a list of whitespace-separated expressions: // Key=AnsiEscape(,AnsiEscape)* if (formats.length > 1) { final String stylesStr = formats[1]; - map = AnsiEscape.createMap( + final Map map = AnsiEscape.createMap( stylesStr.split("\\s", -1), new String[] {"BeginToken", "EndToken", "Style"}, ","); - } else { - map = defaultStyleMap; - } - // Handle the special tokens - beginToken = Objects.toString(map.remove("BeginToken"), BEGIN_TOKEN); - beginTokenLen = beginToken.length(); - endToken = Objects.toString(map.remove("EndToken"), END_TOKEN); - endTokenLen = endToken.length(); - final String predefinedStyle = map.remove("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 styleMap = new HashMap<>(map.size()); - if (predefinedStyle != null) { - final Map predefinedMap = PREFEDINED_STYLE_MAPS.get(predefinedStyle); - if (predefinedMap != null) { - map.putAll(predefinedMap); - } else { - LOGGER.warn( - "Unknown predefined map name {}, pick one of {}", - predefinedStyle, - PREFEDINED_STYLE_MAPS.keySet()); + // Create style map + final Map styleMap = new HashMap<>(map.size() + defaultStyleMap.size()); + defaultStyleMap.forEach((k, v) -> styleMap.put(toRootUpperCase(k), v)); + if (predefinedStyle != null) { + final Map predefinedMap = PREFEDINED_STYLE_MAPS.get(predefinedStyle); + if (predefinedMap != null) { + map.putAll(predefinedMap); + } else { + LOGGER.warn( + "Unknown predefined map name {}, pick one of {}", + predefinedStyle, + PREFEDINED_STYLE_MAPS.keySet()); + } } + styleMap.putAll(map); + this.styleMap = Collections.unmodifiableMap(styleMap); + } else { + beginToken = BEGIN_TOKEN; + endToken = END_TOKEN; + this.styleMap = Collections.unmodifiableMap(defaultStyleMap); } - styleMap.putAll(map); - this.styleMap = styleMap; + beginTokenLen = beginToken.length(); + endTokenLen = endToken.length(); } /** From 3c4802b54c547ce228b7b9815397c927c2c1ead9 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 7 Oct 2024 10:09:06 +0200 Subject: [PATCH 6/7] Remove JANSI library support --- .../log4j/core/test/TestConstants.java | 2 - .../log4j/core/test/categories/Layouts.java | 2 - .../src/main/resources/Log4j-config.xsd | 4 +- .../ConsoleAppenderAnsiMessagesMain.java | 5 +- .../ConsoleAppenderAnsiStyleJira180Main.java | 8 +- .../ConsoleAppenderAnsiStyleJira272Main.java | 8 +- .../ConsoleAppenderAnsiStyleJira319Main.java | 8 +- .../ConsoleAppenderAnsiStyleLayoutMain.java | 6 +- ...onsoleAppenderAnsiStyleNameLayoutMain.java | 3 +- ...=> ConsoleAppenderAnsiXExceptionMain.java} | 15 +-- ...oleAppenderDefaultSuppressedThrowable.java | 11 +- ...oleAppenderHighlightLayoutDefaultMain.java | 3 +- .../ConsoleAppenderHighlightLayoutMain.java | 3 +- ...enderJira1002ShortThrowableLayoutMain.java | 4 +- .../ConsoleAppenderNoAnsiStyleLayoutMain.java | 9 +- .../core/appender/ConsoleAppenderTest.java | 3 - .../appender/JansiConsoleAppenderJira965.java | 28 ----- .../core/impl/ThrowableFormatOptionsTest.java | 26 ++--- ...rerTest.java => AnsiTextRendererTest.java} | 4 +- ...est.java => MessageAnsiConverterTest.java} | 4 +- .../pattern/MessageStyledConverterTest.java | 2 +- .../core/pattern/StyleConverterTest.java | 3 - log4j-core/pom.xml | 7 -- .../log4j/core/appender/ConsoleAppender.java | 1 - .../core/impl/ThrowableFormatOptions.java | 6 +- .../log4j/core/layout/PatternLayout.java | 3 +- ...extRenderer.java => AnsiTextRenderer.java} | 17 ++- .../core/pattern/MessagePatternConverter.java | 2 +- .../logging/log4j/core/util/Loader.java | 20 ---- .../src/main/resources/Log4j-config.xsd | 4 +- .../.log4j-plugin-processing-activator | 1 - log4j-jansi/pom.xml | 106 ------------------ .../jansi/JansiConsoleStreamSupplier.java | 56 --------- .../logging/log4j/jansi/JansiProperties.java | 27 ----- .../logging/log4j/jansi/package-info.java | 22 ---- .../ConsoleAppenderJAnsiMessageMain.java | 66 ----------- .../jansi/JansiConsoleStreamSupplierTest.java | 70 ------------ .../ConsoleAppenderJAnsiMessageMain.xml | 33 ------ .../JansiConsoleStreamSupplierTest.xml | 34 ------ pom.xml | 14 --- .../.3.x.x/1736_split_jansi_support.xml | 2 +- src/site/antora/antora.tmpl.yml | 1 - src/site/antora/antora.yml | 1 - .../antora/modules/ROOT/pages/components.adoc | 18 --- .../modules/ROOT/pages/manual/appenders.adoc | 43 +------ .../ROOT/pages/manual/pattern-layout.adoc | 35 ++---- .../ROOT/pages/manual/systemproperties.adoc | 7 -- .../ROOT/partials/components/log4j-jansi.adoc | 41 ------- .../systemproperties/properties-jansi.adoc | 33 ------ 49 files changed, 85 insertions(+), 746 deletions(-) rename log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/{ConsoleAppenderJAnsiXExceptionMain.java => ConsoleAppenderAnsiXExceptionMain.java} (78%) delete mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java rename log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/{JAnsiTextRendererTest.java => AnsiTextRendererTest.java} (94%) rename log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/{MessageJansiConverterTest.java => MessageAnsiConverterTest.java} (94%) rename log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/{JAnsiTextRenderer.java => AnsiTextRenderer.java} (93%) delete mode 100644 log4j-jansi/.log4j-plugin-processing-activator delete mode 100644 log4j-jansi/pom.xml delete mode 100644 log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java delete mode 100644 log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java delete mode 100644 log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java delete mode 100644 log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java delete mode 100644 log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java delete mode 100644 log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml delete mode 100644 log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml delete mode 100644 src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc delete mode 100644 src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestConstants.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestConstants.java index 21a60e631d9..6ba7b9a19e4 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestConstants.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestConstants.java @@ -67,8 +67,6 @@ private TestConstants() {} public static final String CONFIGURATION_USE_PRECISE_CLOCK = CONFIGURATION + "usePreciseClock"; - public static final String CONSOLE_JANSI_ENABLED = "log4j.console.jansiEnabled"; - private static final String GC = "log4j.gc."; public static final String GC_ENABLE_DIRECT_ENCODERS = GC + "enableDirectEncoders"; 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 4b78f1fdc47..3838ab71d6e 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 @@ -21,6 +21,4 @@ */ public interface Layouts { interface Csv {} - - interface Jansi {} } diff --git a/log4j-core-test/src/main/resources/Log4j-config.xsd b/log4j-core-test/src/main/resources/Log4j-config.xsd index c17d1c59e3f..3ff224097a0 100644 --- a/log4j-core-test/src/main/resources/Log4j-config.xsd +++ b/log4j-core-test/src/main/resources/Log4j-config.xsd @@ -696,7 +696,7 @@ Write directly to java.io.FileDescriptor and bypass java.lang.System.out/.err. Can give up to 10x performance boost when the - output is redirected to file or other process. Cannot be used with Jansi on Windows. Cannot be used with follow. + output is redirected to file or other process. Cannot be used with follow. @@ -704,7 +704,7 @@ Identifies whether the appender honors reassignments of System.out or System.err via System.setOut or System.setErr made after - configuration. Note that the follow attribute cannot be used with Jansi on Windows. Cannot be used with direct. + configuration. Cannot be used with direct. 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 3088ca2bc1b..a44d064ab49 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 @@ *

* *
- * 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
  * 
*/ 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 dffc5b42893..bd8491c6667 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.config.Configurator; /** - * Tests https://issues.apache.org/jira/browse/LOG4J2-180 + * Tests LOG4J2-180 *

* Running from a Windows command line from the root of the project: *

* *
- * 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
  * 
*/ 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 0f2844abe77..65a4ed1ee79 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.config.Configurator; /** - * Tests https://issues.apache.org/jira/browse/LOG4J2-272 + * Tests LOG4J2-272 *

* Running from a Windows command line from the root of the project: *

*
- * 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
  * 
*/ 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 48681a85f71..957a58e9bf6 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.config.Configurator; /** - * Tests https://issues.apache.org/jira/browse/LOG4J2-319 + * Tests LOG4J2-319 *

* Running from a Windows command line from the root of the project: *

* *
- * 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
  * 
*/ 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 87730d9a5a4..2ef1c52c9fa 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 @@ * * or: *
- * 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
  * 
* */ @@ -54,11 +54,9 @@ public void test() { } 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 9088b75d47a..5cdc81ee452 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 d681e02c754..ee062add98e 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.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 @@ *

* *
- * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiXExceptionMain test
+ * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiXExceptionMain test
  * 
* * or, on Windows: * *
- * 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
  * 
* */ -@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 void test() { } 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 8553eb3f1d0..2432df32c93 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 @@ *

* *
- * 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
  * 
*/ 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 @@ static void test(final String[] args, final String config) { 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 7a1b7cf616e..d9b5eeba270 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 dbb6958ea25..ad86c245707 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/ConsoleAppenderJira1002ShortThrowableLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java index b75e3e03259..b1177acdd25 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 @@ */ 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 1c35103862e..1165fc6a609 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 @@ *

* *
- * 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
  * 
*/ public class ConsoleAppenderNoAnsiStyleLayoutMain { @@ -43,12 +43,11 @@ private static void logThrowableFromMethod() { 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/ConsoleAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java index 5eba5acbd73..4e77e0c4331 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java @@ -33,9 +33,7 @@ import org.apache.logging.log4j.core.appender.ConsoleAppender.Target; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.core.test.TestConstants; import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.test.junit.SetTestProperty; import org.apache.logging.log4j.util.Strings; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,7 +42,6 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -@SetTestProperty(key = TestConstants.CONSOLE_JANSI_ENABLED, value = "false") public class ConsoleAppenderTest { ByteArrayOutputStream baos; 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 8bdd3e13efe..00000000000 --- 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 339c1b0e6d2..5323eace0ec 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 @@ -28,7 +28,7 @@ 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.AnsiTextRenderer; import org.apache.logging.log4j.core.pattern.TextRenderer; import org.apache.logging.log4j.util.Strings; import org.junit.jupiter.api.Test; @@ -131,9 +131,9 @@ void testFullAnsiEmptyConfig() { private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertInstanceOf(JAnsiTextRenderer.class, textRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map styleMap = jansiRenderer.getStyleMap(); + assertInstanceOf(AnsiTextRenderer.class, textRenderer); + final AnsiTextRenderer ansiRenderer = (AnsiTextRenderer) textRenderer; + final Map styleMap = ansiRenderer.getStyleMap(); // We have defaults assertFalse(styleMap.isEmpty()); assertNotNull(styleMap.get(toRootUpperCase("Name"))); @@ -148,9 +148,9 @@ void testFullAnsiWithCustomStyle() { test(new String[] {"full", "ansi(Warning=red)"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertInstanceOf(JAnsiTextRenderer.class, textRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map styleMap = jansiRenderer.getStyleMap(); + assertInstanceOf(AnsiTextRenderer.class, textRenderer); + final AnsiTextRenderer ansiRenderer = (AnsiTextRenderer) textRenderer; + final Map styleMap = ansiRenderer.getStyleMap(); assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); } @@ -166,9 +166,9 @@ void testFullAnsiWithCustomStyles() { null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertInstanceOf(JAnsiTextRenderer.class, textRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map styleMap = jansiRenderer.getStyleMap(); + assertInstanceOf(AnsiTextRenderer.class, textRenderer); + final AnsiTextRenderer ansiRenderer = (AnsiTextRenderer) textRenderer; + final Map 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")); @@ -186,9 +186,9 @@ void testFullAnsiWithCustomComplexStyles() { null); final TextRenderer textRenderer = tfo.getTextRenderer(); assertNotNull(textRenderer); - assertInstanceOf(JAnsiTextRenderer.class, textRenderer); - final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; - final Map styleMap = jansiRenderer.getStyleMap(); + assertInstanceOf(AnsiTextRenderer.class, textRenderer); + final AnsiTextRenderer ansiRenderer = (AnsiTextRenderer) textRenderer; + final Map 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"))) 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/AnsiTextRendererTest.java similarity index 94% rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiTextRendererTest.java index 32a07d35f8f..17e590a86a2 100644 --- 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/AnsiTextRendererTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class JAnsiTextRendererTest { +class AnsiTextRendererTest { public static Stream testRendering() { return Stream.of( @@ -47,7 +47,7 @@ public static Stream testRendering() { @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 AnsiTextRenderer renderer = new AnsiTextRenderer(new String[] {"ansi", format}, Map.of()); final StringBuilder actual = new StringBuilder(); renderer.render(new StringBuilder(text), actual); assertThat(actual.toString()) 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 293db30ba99..670fefd097f 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.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 void setUp(final LoggerContext context, @Named("List") final ListAppender @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 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 71fb6fbc77b..06ab681fedd 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 void setUp(final LoggerContext context, @Named("List") final ListAppender @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 msgs = app.getMessages(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java index 6a5a77951fa..01b3f51d306 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java @@ -26,19 +26,16 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.test.TestConstants; import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.core.test.junit.Named; import org.apache.logging.log4j.test.ListStatusListener; -import org.apache.logging.log4j.test.junit.SetTestProperty; import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.apache.logging.log4j.util.Strings; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -@SetTestProperty(key = TestConstants.CONSOLE_JANSI_ENABLED, value = "true") public class StyleConverterTest { private static final String EXPECTED = diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml index 8819a023a93..b2b00b30d82 100644 --- a/log4j-core/pom.xml +++ b/log4j-core/pom.xml @@ -98,13 +98,6 @@ log4j-plugins
- - - org.fusesource.jansi - jansi - true - - 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 f25c3924bdb..10e42356b03 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 @@ -54,7 +54,6 @@ public final class ConsoleAppender extends AbstractOutputStreamAppender { public static final String PLUGIN_NAME = "Console"; - private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; private static final ConsoleManagerFactory factory = new ConsoleManagerFactory(); private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT; private static final AtomicInteger COUNT = new AtomicInteger(); 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 34d02d68454..07a5d4ab249 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 @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Scanner; -import org.apache.logging.log4j.core.pattern.JAnsiTextRenderer; +import org.apache.logging.log4j.core.pattern.AnsiTextRenderer; import org.apache.logging.log4j.core.pattern.PlainTextRenderer; import org.apache.logging.log4j.core.pattern.TextRenderer; import org.apache.logging.log4j.core.util.Patterns; @@ -290,8 +290,8 @@ public static ThrowableFormatOptions newInstance(String[] options) { 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); + ansiRenderer = new AnsiTextRenderer( + new String[] {null, styleMapStr}, AnsiTextRenderer.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 a0bb6b0a146..b9b3f9f7dc9 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 @@ -644,8 +644,7 @@ public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { /** * @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 setDisableAnsi(final boolean disableAnsi) { this.disableAnsi = disableAnsi; 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/AnsiTextRenderer.java similarity index 93% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiTextRenderer.java index e58a64de21e..84382815b5e 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/AnsiTextRenderer.java @@ -33,7 +33,9 @@ /** * Renders an input as ANSI escaped output. *

- * Uses the JAnsi rendering syntax as the default to render a message into an ANSI escaped string. + * Uses the + * JLine AnsiRenderer syntax + * to render a message into an ANSI escaped string. *

*

* The default syntax for embedded ANSI codes is: @@ -72,10 +74,13 @@ * logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue()); * * - * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as - * Apache 2.0.) + *

+ * Note: this class was originally copied and then heavily modified from + * JAnsi/JLine AnsiRenderer, + * licensed under an Apache Software License, version 2.0. + *

*/ -public final class JAnsiTextRenderer implements TextRenderer { +public final class AnsiTextRenderer implements TextRenderer { private static final Logger LOGGER = StatusLogger.getLogger(); @@ -165,7 +170,7 @@ private static Map.Entry entry(final String name, final AnsiEsca private final int endTokenLen; private final Map styleMap; - public JAnsiTextRenderer(final String[] formats, final Map defaultStyleMap) { + public AnsiTextRenderer(final String[] formats, final Map defaultStyleMap) { // The format string is a list of whitespace-separated expressions: // Key=AnsiEscape(,AnsiEscape)* if (formats.length > 1) { @@ -283,7 +288,7 @@ public Map getStyleMap() { @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 a53a4613656..f323dd93064 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 @@ -52,7 +52,7 @@ private static TextRenderer loadMessageRenderer(final String[] options) { for (final String option : options) { switch (toRootUpperCase(option)) { case "ANSI": - return new JAnsiTextRenderer(options, JAnsiTextRenderer.DEFAULT_MESSAGE_STYLE_MAP); + return new AnsiTextRenderer(options, AnsiTextRenderer.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 84667ce9483..2e86daf2ca3 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 @@ -275,26 +275,6 @@ public static T newInstanceOf(final Class clazz) } } - /** - * Determines if a named Class can be loaded or not. - * - * @param className The class name. - * @return {@code true} if the class could be found or {@code false} otherwise. - */ - public static boolean isClassAvailable(final String className) { - final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(getClassLoader()); - return LoaderUtil.isClassAvailable(className); - } finally { - Thread.currentThread().setContextClassLoader(contextClassLoader); - } - } - - public static boolean isJansiAvailable() { - return isClassAvailable("org.fusesource.jansi.AnsiRenderer"); - } - /** * Loads a class by name. This method respects the {@link LoaderProperties#ignoreTccl()} Log4j property. If this * property is diff --git a/log4j-core/src/main/resources/Log4j-config.xsd b/log4j-core/src/main/resources/Log4j-config.xsd index c17d1c59e3f..3ff224097a0 100644 --- a/log4j-core/src/main/resources/Log4j-config.xsd +++ b/log4j-core/src/main/resources/Log4j-config.xsd @@ -696,7 +696,7 @@ Write directly to java.io.FileDescriptor and bypass java.lang.System.out/.err. Can give up to 10x performance boost when the - output is redirected to file or other process. Cannot be used with Jansi on Windows. Cannot be used with follow. + output is redirected to file or other process. Cannot be used with follow. @@ -704,7 +704,7 @@ Identifies whether the appender honors reassignments of System.out or System.err via System.setOut or System.setErr made after - configuration. Note that the follow attribute cannot be used with Jansi on Windows. Cannot be used with direct. + configuration. Cannot be used with direct. diff --git a/log4j-jansi/.log4j-plugin-processing-activator b/log4j-jansi/.log4j-plugin-processing-activator deleted file mode 100644 index ba133f36961..00000000000 --- a/log4j-jansi/.log4j-plugin-processing-activator +++ /dev/null @@ -1 +0,0 @@ -This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-jansi/pom.xml b/log4j-jansi/pom.xml deleted file mode 100644 index 3b6f0c8454c..00000000000 --- a/log4j-jansi/pom.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - 4.0.0 - - org.apache.logging.log4j - log4j - ${revision} - ../log4j-parent - - - log4j-jansi - Apache Log4j Core: JANSI support - Support for JANSI library for Log4j Core. - - - 3.26.3 - - - - - - org.jline - jansi-core - ${jline.version} - - - - - - - - org.apache.logging.log4j - log4j-core - - - - org.apache.logging.log4j - log4j-kit - - - - org.apache.logging.log4j - log4j-plugins - - - - org.jline - jansi-core - - - - org.assertj - assertj-core - test - - - - org.junit.jupiter - junit-jupiter-api - test - - - - org.junit.jupiter - junit-jupiter-engine - test - - - - org.apache.logging.log4j - log4j-core-test - test - - - org.hamcrest - hamcrest - - - junit - junit - - - org.junit.vintage - junit-vintage-engine - - - - - - diff --git a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java deleted file mode 100644 index c00313e3f43..00000000000 --- a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplier.java +++ /dev/null @@ -1,56 +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.jansi; - -import java.io.OutputStream; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.ConsoleAppender; -import org.apache.logging.log4j.core.util.CloseShieldOutputStream; -import org.apache.logging.log4j.kit.env.PropertyEnvironment; -import org.apache.logging.log4j.plugins.Namespace; -import org.apache.logging.log4j.plugins.Ordered; -import org.apache.logging.log4j.plugins.Plugin; -import org.apache.logging.log4j.status.StatusLogger; -import org.jline.jansi.AnsiConsole; -import org.jspecify.annotations.Nullable; - -/** - * Uses the JAnsi library to provide standard output and error. - */ -@Plugin -@Namespace(ConsoleAppender.ConsoleStreamSupplier.NAMESPACE) -@Ordered(0) -public class JansiConsoleStreamSupplier implements ConsoleAppender.ConsoleStreamSupplier { - - private static final Logger LOGGER = StatusLogger.getLogger(); - - @Override - public @Nullable OutputStream getOutputStream( - boolean follow, boolean direct, ConsoleAppender.Target target, PropertyEnvironment properties) { - if (properties.getProperty(JansiProperties.class).jansiEnabled()) { - if (follow || direct) { - LOGGER.error( - "Can not use neither `follow` nor `direct` on ConsoleAppender, since JAnsi library is used."); - return null; - } - return new CloseShieldOutputStream( - target == ConsoleAppender.Target.SYSTEM_ERR ? AnsiConsole.err() : AnsiConsole.out()); - } - LOGGER.debug("Ignoring JAnsi library since configuration property `log4j.console.jansiEnabled` is false."); - return null; - } -} diff --git a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java deleted file mode 100644 index 8a48b1d8a55..00000000000 --- a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/JansiProperties.java +++ /dev/null @@ -1,27 +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.jansi; - -import org.apache.logging.log4j.kit.env.Log4jProperty; - -/** - * Properties to tune console output. - * - * @param jansiEnabled If {@code true}, the JAnsi library will be used whenever available. - */ -@Log4jProperty(name = "console") -public record JansiProperties(@Log4jProperty(defaultValue = "true") boolean jansiEnabled) {} diff --git a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java b/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java deleted file mode 100644 index cc1d9000c6f..00000000000 --- a/log4j-jansi/src/main/java/org/apache/logging/log4j/jansi/package-info.java +++ /dev/null @@ -1,22 +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. - */ -@Export -@Version("3.0.0") -package org.apache.logging.log4j.jansi; - -import org.osgi.annotation.bundle.Export; -import org.osgi.annotation.versioning.Version; diff --git a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java deleted file mode 100644 index cbb20094f97..00000000000 --- a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/ConsoleAppenderJAnsiMessageMain.java +++ /dev/null @@ -1,66 +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.jansi; - -import static org.jline.jansi.Ansi.Color.CYAN; -import static org.jline.jansi.Ansi.Color.RED; -import static org.jline.jansi.Ansi.ansi; - -import java.net.URI; -import java.net.URISyntaxException; -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.util.NetUtils; - -/** - * 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. - *

- * Running from a Windows command line from the root of the project: - *

- * - *
- * mvn exec:java -Dexec.mainClass=org.apache.logging.log4j.jansi.ConsoleAppenderJAnsiMessageMain
- * 
- */ -public class ConsoleAppenderJAnsiMessageMain { - - public static void main(final String[] args) throws URISyntaxException { - new ConsoleAppenderJAnsiMessageMain().test(args); - } - - public void test(final String[] args) throws URISyntaxException { - final URI config = args == null || args.length == 0 - ? ConsoleAppenderJAnsiMessageMain.class - .getResource("/ConsoleAppenderJAnsiMessageMain.xml") - .toURI() - : NetUtils.toURI(args[0]); - try (final LoggerContext ctx = - Configurator.initialize(getClass().getName(), getClass().getClassLoader(), config)) { - final Logger logger = LogManager.getLogger(); - logger.info(ansi().fg(RED).a("Hello").fg(CYAN).a(" World").reset()); - // JAnsi format: - // logger.info("@|red Hello|@ @|cyan World|@"); - for (final Entry entry : System.getProperties().entrySet()) { - logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue()); - } - } - } -} diff --git a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java b/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java deleted file mode 100644 index 4c8816a182a..00000000000 --- a/log4j-jansi/src/test/java/org/apache/logging/log4j/jansi/JansiConsoleStreamSupplierTest.java +++ /dev/null @@ -1,70 +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.jansi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import org.apache.logging.log4j.core.appender.ConsoleAppender; -import org.apache.logging.log4j.core.appender.OutputStreamManager; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; -import org.apache.logging.log4j.core.util.CloseShieldOutputStream; -import org.apache.logging.log4j.plugins.Named; -import org.apache.logging.log4j.test.junit.SetTestProperty; -import org.jline.jansi.AnsiConsole; -import org.junit.jupiter.api.Test; - -public class JansiConsoleStreamSupplierTest { - - @Test - @LoggerContextSource("JansiConsoleStreamSupplierTest.xml") - void usesJansiByDefault(final @Named("CONSOLE") ConsoleAppender appender) { - assumeThat(AnsiConsole.out()).isNotEqualTo(System.out); - - final OutputStreamManager manager = appender.getManager(); - assertOutputStreamIsEqual(AnsiConsole.out(), manager); - } - - @Test - @SetTestProperty(key = "log4j.console.jansiEnabled", value = "false") - @LoggerContextSource("JansiConsoleStreamSupplierTest.xml") - void whenJansiDisabled_usesSystemOut(final @Named("CONSOLE") ConsoleAppender appender) { - assumeThat(AnsiConsole.out()).isNotEqualTo(System.out); - - final OutputStreamManager manager = appender.getManager(); - assertOutputStreamIsEqual(System.out, manager); - } - - private static void assertOutputStreamIsEqual(final OutputStream expected, final OutputStreamManager manager) { - final OutputStream wrappedActual = assertDoesNotThrow(() -> { - final Method getOutputStream = OutputStreamManager.class.getDeclaredMethod("getOutputStream"); - getOutputStream.setAccessible(true); - return (OutputStream) getOutputStream.invoke(manager); - }); - assertThat(wrappedActual).isInstanceOf(CloseShieldOutputStream.class); - final OutputStream actual = assertDoesNotThrow(() -> { - final Field delegate = CloseShieldOutputStream.class.getDeclaredField("delegate"); - delegate.setAccessible(true); - return (OutputStream) delegate.get(wrappedActual); - }); - assertThat(actual).isEqualTo(expected); - } -} diff --git a/log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml b/log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml deleted file mode 100644 index 249af27ef7f..00000000000 --- a/log4j-jansi/src/test/resources/ConsoleAppenderJAnsiMessageMain.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - diff --git a/log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml b/log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml deleted file mode 100644 index 4c33f668edb..00000000000 --- a/log4j-jansi/src/test/resources/JansiConsoleStreamSupplierTest.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index ab5ab192281..2b37e957625 100644 --- a/pom.xml +++ b/pom.xml @@ -243,7 +243,6 @@ log4j-csv log4j-docker log4j-gc-test - log4j-jansi log4j-jctools log4j-jdbc log4j-jdbc-dbcp2 @@ -344,7 +343,6 @@ 1.3.4 1.2.21 1.11.0 - 1.18 18.3.12 3.0.0-beta2 3.0.0-alpha1 @@ -457,12 +455,6 @@ ${log4j-api.version}
- - org.apache.logging.log4j - log4j-jansi - ${project.version} - - org.apache.logging.log4j log4j-jctools @@ -823,12 +815,6 @@ ${site-flume.version} - - org.fusesource.jansi - jansi - ${site-jansi.version} - - com.sleepycat je diff --git a/src/changelog/.3.x.x/1736_split_jansi_support.xml b/src/changelog/.3.x.x/1736_split_jansi_support.xml index 2220a452047..1005cb42a05 100644 --- a/src/changelog/.3.x.x/1736_split_jansi_support.xml +++ b/src/changelog/.3.x.x/1736_split_jansi_support.xml @@ -4,5 +4,5 @@ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="changed"> - Move JAnsi support to a new `log4j-jansi` module and upgrade to version 2.x of the library. + Remove JAnsi library support, since Windows console supports ANSI escapes now. diff --git a/src/site/antora/antora.tmpl.yml b/src/site/antora/antora.tmpl.yml index 632124c8676..b568da3457c 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}" je-version: "${site-je.version}" log4j-api-version: "${log4j-api.version}" log4j-core-version: "${site-log4j-core.version}" diff --git a/src/site/antora/antora.yml b/src/site/antora/antora.yml index 9fbdd43fcac..e4d86990b46 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" je-version: "1.2.3-je" log4j-api-version: "1.2.3-api" log4j-core-version: "1.2.3-core" diff --git a/src/site/antora/modules/ROOT/pages/components.adoc b/src/site/antora/modules/ROOT/pages/components.adoc index 1f01a3f47e4..cbc79d63be6 100644 --- a/src/site/antora/modules/ROOT/pages/components.adoc +++ b/src/site/antora/modules/ROOT/pages/components.adoc @@ -216,24 +216,6 @@ for more information. include::partial$components/log4j-flume-ng.adoc[] -[#log4j-jansi] -== `log4j-jansi` - -|=== -| JPMS module -| `org.apache.logging.log4j.jansi` -|=== - -The `log4j-jansi` artifact contains an extension for the -xref:manual/appenders.adoc#ConsoleAppender[Console Appender] -that uses the -https://fusesource.github.io/jansi/[JAnsi library] -to handle ANSI escapes. - -See xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[`log4j.console.jansiEnabled`] for more details. - -include::partial$components/log4j-jansi.adoc[] - [#log4j-jctools] == `log4j-jctools` diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders.adoc index 28fa1dd8c9b..aee25f66224 100644 --- a/src/site/antora/modules/ROOT/pages/manual/appenders.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/appenders.adoc @@ -214,7 +214,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. @@ -234,39 +234,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. ==== -[#ConsoleAppender-jansi] -`JANSI`:: -If the -xref:components.adoc#log4j-jansi[`log4j-jansi` extension] -is available, the Console appender will use the -https://fusesource.github.io/jansi/[JAnsi library] -to handle ANSI escapes. -The library: -+ --- -* Implement ANSI escape handling on old Windows consoles. -* Strips ANSI escape codes if process output is being redirected and not attached to a terminal --- -+ -See https://github.com/fusesource/jansi?tab=readme-ov-file#features[Features of JAnsi] for more details. -+ -[TIP] -==== -Support for ANSI escape sequences was added to Windows 10 console around 2017. -See -https://superuser.com/questions/413073/windows-console-with-ansi-colors-handling[Windows console with ANSI colors handling] -for more details. -==== -+ -The `JANSI` mode can be disabled by setting the -xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[`log4j.console.jansiEnabled`] -configuration attribute to `false`. -Additional runtime dependencies are required to use JAnsi: -+ --- -include::partial$components/log4j-jansi.adoc[] --- - [#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 -<> -and -xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[JANSI support]. +<>. | [[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 -<> -and -xref:manual/systemproperties.adoc#log4j.console.jansiEnabled[JANSI support]. +<>. | [[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 a4300d30462..57c29d892f8 100644 --- a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc @@ -211,12 +211,6 @@ The optional footer to include at the bottom of each log file If `true`, do not output ANSI escape codes. -[NOTE] -==== -If you use a Console Appender in xref:manual/appenders.adoc#ConsoleAppender-jansi[`JANSI` mode], you should keep this setting at its default `false` value. -The JAnsi library will take care of stripping ANSI Escape if the output of the console is not a terminal. -==== - [#plugin-attr-noConsoleNoAnsi] ==== `noConsoleNoAnsi` @@ -226,7 +220,7 @@ The JAnsi library will take care of stripping ANSI Escape if the output of the c |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 @@ -745,7 +739,7 @@ fqcn ==== Highlight Adds ANSI colors to the result of the enclosed pattern based on the current event's {log4j2-url}/manual/customloglevels.html[logging level]. -Windows users should refer to <>. +Windows users should refer to <>. .link:../javadoc/log4j-core/org/apache/logging/log4j/core/pattern/HighlightConverter.html[`HighlightConverter`] specifier grammar [source,text] @@ -1047,7 +1041,7 @@ message{lookups}{ansi} ---- Add `\{ansi}` to render messages with ANSI escape codes. -Windows users should refer to <>. +Windows users should refer to <>. The default syntax for embedded ANSI codes is: @@ -1241,7 +1235,7 @@ The counter is a static variable, so will only be unique within applications tha Use ANSI escape sequences to style the result of the enclosed pattern. The syntax of the `style_expression` parameter is described in <>. -Windows users should also refer to <>. +Windows users should also refer to <>. .link:../javadoc/log4j-core/org/apache/logging/log4j/core/pattern/StyleConverter.html[`StyleConverter`] specifier grammar [source,text] @@ -1591,22 +1585,17 @@ If your terminal supports 24-bit colors, you can specify: * the text color using the `#rrggbb` syntax, e.g. `#dc143c` will color your text crimson, * the background color using the `bg_#rrggbb` syntax, e.g. `bg_#87ceeb` will use a sky blue background. -[#jansi] +[#ansi-windows] ==== 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#log4j.console.jansiEnabled[the `log4j.console.jansiEnabled` system property] to `true`. -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 is disabled by default in `cmd.exe` on Windows. +To enable ANSI escape sequences, create a registry key of name `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 941748710c3..ebbe0f2174d 100644 --- a/src/site/antora/modules/ROOT/pages/manual/systemproperties.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/systemproperties.adoc @@ -170,13 +170,6 @@ include::partial$manual/systemproperties/properties-log-event.adoc[leveloffset=+ 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-log4j-core-thread-context] === Thread context diff --git a/src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc b/src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc deleted file mode 100644 index d7864aff767..00000000000 --- a/src/site/antora/modules/ROOT/partials/components/log4j-jansi.adoc +++ /dev/null @@ -1,41 +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. -//// - -[tabs] -==== -Maven:: -+ -We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. -+ -[source,xml] ----- - - org.apache.logging.log4j - log4j-jansi - runtime - ----- - -Gradle:: -+ -We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. -+ -[source,groovy] ----- -runtimeOnly 'org.apache.logging.log4j:log4j-jansi' ----- -==== \ No newline at end of file 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 ca43e6bc296..00000000000 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-jansi.adoc +++ /dev/null @@ -1,33 +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=log4j.console.jansiEnabled] -== `log4j.console.jansiEnabled` - -[cols="1h,5"] -|=== -| Env. variable | `LOG4J_CONSOLE_JANSI_ENABLED` -| Type | `boolean` -| Default value | `true` -|=== - -If the -xref:components.adoc#log4j-jansi[`log4j-jansi`] -extension is available and this value is `true`, the -xref:manual/appenders.adoc#ConsoleAppender[Console Appender] -will use the -https://fusesource.github.io/jansi/[JAnsi library] -to handle ANSI escapes. From 0f2a1684c3e8494721ba4e96cb81e4ec275cadde Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 7 Oct 2024 10:28:34 +0200 Subject: [PATCH 7/7] Remove `ConsoleStreamSupplier` interface --- .../log4j/core/appender/ConsoleAppender.java | 136 +++++++++++------ .../DefaultConsoleStreamSupplier.java | 139 ------------------ 2 files changed, 87 insertions(+), 188 deletions(-) delete mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java 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 10e42356b03..4e6dbb47ae0 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 @@ -16,28 +16,23 @@ */ package org.apache.logging.log4j.core.appender; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; -import java.util.List; -import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.appender.internal.DefaultConsoleStreamSupplier; -import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.util.CloseShieldOutputStream; -import org.apache.logging.log4j.kit.env.PropertyEnvironment; import org.apache.logging.log4j.plugins.Configurable; -import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginFactory; -import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.plugins.validation.constraints.Required; -import org.jspecify.annotations.Nullable; /** * Appends log events to System.out or System.err using a layout specified by the user. The @@ -166,30 +161,15 @@ public B setDirect(final boolean shouldDirect) { @Override public ConsoleAppender build() { - final Layout layout = getOrCreateLayout(target.getDefaultCharset()); - - final Configuration configuration = getConfiguration(); - final PropertyEnvironment propertyEnvironment; - final List suppliers; - if (configuration != null) { - propertyEnvironment = configuration.getLoggerContext() != null - ? configuration.getLoggerContext().getEnvironment() - : PropertyEnvironment.getGlobal(); - - suppliers = configuration.getComponent(new @Namespace(ConsoleStreamSupplier.NAMESPACE) Key<>() {}); - } else { - propertyEnvironment = PropertyEnvironment.getGlobal(); - suppliers = List.of(new DefaultConsoleStreamSupplier()); + if (direct && follow) { + LOGGER.error("Cannot use both `direct` and `follow` on ConsoleAppender."); + return null; } + final Layout layout = getOrCreateLayout(target.getDefaultCharset()); - final OutputStream stream = suppliers.stream() - .map(s -> s.getOutputStream(follow, direct, target, propertyEnvironment)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - if (stream == null) { - LOGGER.warn("No output stream found for target {}", target); - } + OutputStream stream = direct + ? getDirectOutputStream(target) + : follow ? getFollowOutputStream(target) : getDefaultOutputStream(target); final String managerName = target.name() + '.' + follow + '.' + direct; final OutputStreamManager manager = @@ -207,6 +187,83 @@ private static OutputStreamManager getDefaultManager(final Layout layout) { 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 getDirectOutputStream(Target target) { + return new CloseShieldOutputStream( + new FileOutputStream(target == Target.SYSTEM_OUT ? FileDescriptor.out : FileDescriptor.err)); + } + + private static OutputStream getFollowOutputStream(Target target) { + return target == Target.SYSTEM_OUT ? new SystemOutStream() : new SystemErrStream(); + } + + /** + * An implementation of OutputStream that redirects to the current System.err. + */ + private static class SystemErrStream extends OutputStream { + public SystemErrStream() {} + + @Override + public void close() { + // do not close sys err! + } + + @Override + public void flush() { + System.err.flush(); + } + + @Override + public void write(final byte[] b) throws IOException { + System.err.write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + System.err.write(b, off, len); + } + + @Override + public void write(final int b) { + System.err.write(b); + } + } + + /** + * An implementation of OutputStream that redirects to the current System.out. + */ + private static class SystemOutStream extends OutputStream { + public SystemOutStream() {} + + @Override + public void close() { + // do not close sys out! + } + + @Override + public void flush() { + System.out.flush(); + } + + @Override + public void write(final byte[] b) throws IOException { + System.out.write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + System.out.write(b, off, len); + } + + @Override + public void write(final int b) throws IOException { + System.out.write(b); + } + } + /** * Data to pass to factory method.Unable to instantiate */ @@ -250,23 +307,4 @@ public OutputStreamManager createManager(final String name, final FactoryData da public Target getTarget() { return target; } - - /** - * Abstracts the various ways `System.out` can be accessed. - * - * @since 3.0.0 - */ - public interface ConsoleStreamSupplier { - - /** - * The Log4j plugin namespace of plugins implementing this interface. - */ - String NAMESPACE = "Console"; - - /** - * @return Selects the output stream to use or {@code null} in case of error. - */ - @Nullable - OutputStream getOutputStream(boolean follow, boolean direct, Target target, PropertyEnvironment properties); - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java deleted file mode 100644 index 858233c0f80..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/internal/DefaultConsoleStreamSupplier.java +++ /dev/null @@ -1,139 +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.internal; - -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.ConsoleAppender; -import org.apache.logging.log4j.core.util.CloseShieldOutputStream; -import org.apache.logging.log4j.kit.env.PropertyEnvironment; -import org.apache.logging.log4j.plugins.Namespace; -import org.apache.logging.log4j.plugins.Ordered; -import org.apache.logging.log4j.plugins.Plugin; -import org.apache.logging.log4j.status.StatusLogger; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@Plugin -@Namespace(ConsoleAppender.ConsoleStreamSupplier.NAMESPACE) -@Ordered(Ordered.LAST) -@NullMarked -public class DefaultConsoleStreamSupplier implements ConsoleAppender.ConsoleStreamSupplier { - - private static final Logger LOGGER = StatusLogger.getLogger(); - - @Override - @Nullable - public OutputStream getOutputStream( - boolean follow, boolean direct, ConsoleAppender.Target target, PropertyEnvironment properties) { - if (follow && direct) { - LOGGER.error("Cannot use both `follow` and `direct` on ConsoleAppender."); - return null; - } - return switch (target) { - case SYSTEM_ERR -> getErr(follow, direct); - case SYSTEM_OUT -> getOut(follow, direct); - }; - } - - private OutputStream getErr(final boolean follow, final boolean direct) { - if (direct) { - return new CloseShieldOutputStream(new FileOutputStream(FileDescriptor.err)); - } - if (follow) { - return new SystemErrStream(); - } - return new CloseShieldOutputStream(System.err); - } - - private OutputStream getOut(final boolean follow, final boolean direct) { - if (direct) { - return new CloseShieldOutputStream(new FileOutputStream(FileDescriptor.out)); - } - if (follow) { - return new SystemOutStream(); - } - return new CloseShieldOutputStream(System.out); - } - - /** - * An implementation of OutputStream that redirects to the current System.err. - */ - private static class SystemErrStream extends OutputStream { - public SystemErrStream() {} - - @Override - public void close() { - // do not close sys err! - } - - @Override - public void flush() { - System.err.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - System.err.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) { - System.err.write(b, off, len); - } - - @Override - public void write(final int b) { - System.err.write(b); - } - } - - /** - * An implementation of OutputStream that redirects to the current System.out. - */ - private static class SystemOutStream extends OutputStream { - public SystemOutStream() {} - - @Override - public void close() { - // do not close sys out! - } - - @Override - public void flush() { - System.out.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - System.out.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) { - System.out.write(b, off, len); - } - - @Override - public void write(final int b) { - System.out.write(b); - } - } -}