This is an automated email from the ASF dual-hosted git repository. vy pushed a commit to branch feature/LOG4J2-3584-StatusConsoleListener-uses-SimpleLogger in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 5ce3f8d33bca1d4c96db64e5124e9d3682261608 Author: Volkan Yazıcı <[email protected]> AuthorDate: Mon Oct 10 22:33:39 2022 +0200 LOG4J2-3584 Make StatusConsoleListener use SimpleLogger internally. --- .../log4j/status/StatusConsoleListenerTest.java | 170 +++++++++++++++++++++ .../log4j/status/StatusConsoleListener.java | 32 +++- .../apache/logging/log4j/status/StatusLogger.java | 13 +- src/changes/changes.xml | 3 + 4 files changed, 209 insertions(+), 9 deletions(-) diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java new file mode 100644 index 0000000000..68dcf343e7 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java @@ -0,0 +1,170 @@ +/* + * 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.status; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.apache.logging.log4j.simple.SimpleLogger; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; +import org.mockito.Mockito; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class StatusConsoleListenerTest { + + public static final MessageFactory MESSAGE_FACTORY = ParameterizedNoReferenceMessageFactory.INSTANCE; + + @Nested + @ExtendWith(SystemStubsExtension.class) + @ResourceLock(value = Resources.SYSTEM_PROPERTIES) + class SimpleLogger_should_be_used { + + @Test + void test(final SystemProperties properties) throws Exception { + + // Customize the date-time formatting to be passed on to the `SimpleLogger`. + properties.set(StatusLogger.STATUS_DATE_FORMAT, "'LOG4J2-3584 'ss.SSS"); + + // Create the listener. + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final String encoding = "UTF-8"; + final PrintStream printStream = new PrintStream(outputStream, false, encoding); + final StatusConsoleListener listener = new StatusConsoleListener(Level.WARN, printStream); + + // Verify the internal `SimpleLogger`. + Assertions + .assertThat(listener) + .extracting("logger") + .isInstanceOf(SimpleLogger.class); + + // Log a message. + final Message message = MESSAGE_FACTORY.newMessage("foo"); + listener.log(new StatusData( + null, // since ignored by `SimpleLogger` + Level.ERROR, + message, + null, + null)); // as set by `StatusLogger` itself + + // Verify the output. + printStream.flush(); + final String output = outputStream.toString(encoding); + Assertions + .assertThat(output) + .matches("(?s)LOG4J2-3584 \\d{2}\\.\\d{3} ERROR StatusConsoleListener foo\\r?\\n$"); + + } + + } + + @Test + void level_and_stream_should_be_honored() throws Exception { + + // Create the listener. + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final String encoding = "UTF-8"; + final PrintStream printStream = new PrintStream(outputStream, false, encoding); + final StatusConsoleListener listener = new StatusConsoleListener(Level.WARN, printStream); + + // First, log a message that is expected to be logged. + final RuntimeException expectedThrowable = new RuntimeException("expectedThrowable"); + expectedThrowable.setStackTrace(new StackTraceElement[]{ + new StackTraceElement("expectedThrowableClass", "expectedThrowableMethod", "expectedThrowableFile", 1) + }); + final Message expectedMessage = MESSAGE_FACTORY.newMessage("expectedMessage"); + listener.log(new StatusData( + null, // since ignored by `SimpleLogger` + Level.WARN, + expectedMessage, + expectedThrowable, + null)); // as set by `StatusLogger` itself + + // Second, log a message that is expected to be discarded due to its insufficient level. + final RuntimeException discardedThrowable = new RuntimeException("discardedThrowable"); + discardedThrowable.setStackTrace(new StackTraceElement[]{ + new StackTraceElement("discardedThrowableClass", "discardedThrowableMethod", "discardedThrowableFile", 2) + }); + final Message discardedMessage = MESSAGE_FACTORY.newMessage("discardedMessage"); + listener.log(new StatusData( + null, // since ignored by `SimpleLogger` + Level.INFO, + discardedMessage, + discardedThrowable, + null)); // as set by `StatusLogger` itself + + // Collect the output. + printStream.flush(); + final String output = outputStream.toString(encoding); + + // Verify the output. + Assertions + .assertThat(output) + .contains(expectedThrowable.getMessage()) + .contains(expectedMessage.getFormattedMessage()) + .doesNotContain(discardedThrowable.getMessage()) + .doesNotContain(discardedMessage.getFormattedMessage()); + + } + + @Test + void filters_should_be_honored() throws Exception { + + // Create the listener. + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final String encoding = "UTF-8"; + final PrintStream printStream = new PrintStream(outputStream, false, encoding); + final StatusConsoleListener listener = new StatusConsoleListener(Level.TRACE, printStream); + + // Set the filter. + final StackTraceElement caller = new StackTraceElement("callerClass", "callerMethod", "callerFile", 1); + listener.setFilters(caller.getClassName()); + + // Log the message to be filtered. + final Message message = MESSAGE_FACTORY.newMessage("foo"); + listener.log(new StatusData( + caller, + Level.TRACE, + message, + null, + null)); // as set by `StatusLogger` itself + + // Verify the filtering. + printStream.flush(); + final String output = outputStream.toString(encoding); + Assertions.assertThat(output).isEmpty(); + + } + + @Test + void non_system_streams_should_be_closed() throws Exception { + final PrintStream stream = Mockito.mock(PrintStream.class); + final StatusConsoleListener listener = new StatusConsoleListener(Level.WARN, stream); + listener.close(); + Mockito.verify(stream).close(); + } + +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java index 1097483e92..0fd611757b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java @@ -16,21 +16,28 @@ */ package org.apache.logging.log4j.status; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.apache.logging.log4j.simple.SimpleLogger; + import java.io.IOException; import java.io.PrintStream; -import org.apache.logging.log4j.Level; - /** * StatusListener that writes to the Console. */ @SuppressWarnings("UseOfSystemOutOrSystemErr") public class StatusConsoleListener implements StatusListener { - private Level level = Level.FATAL; + private Level level; + private String[] filters; + private final PrintStream stream; + private final Logger logger; + /** * Creates the StatusConsoleListener using the supplied Level. * @param level The Level of status messages that should appear on the console. @@ -52,6 +59,17 @@ public class StatusConsoleListener implements StatusListener { } this.level = level; this.stream = stream; + this.logger = new SimpleLogger( + "StatusConsoleListener", + level, + false, + true, + StatusLogger.DATE_FORMAT_PROVIDED, + false, + StatusLogger.DATE_FORMAT, + ParameterizedNoReferenceMessageFactory.INSTANCE, + StatusLogger.PROPS, + stream); } /** @@ -78,7 +96,12 @@ public class StatusConsoleListener implements StatusListener { @Override public void log(final StatusData data) { if (!filtered(data)) { - stream.println(data.getFormattedStatus()); + logger + // Logging using _only_ the following 4 fields set by `StatusLogger#logMessage()`: + .atLevel(data.getLevel()) + .withThrowable(data.getThrowable()) + .withLocation(data.getStackTraceElement()) + .log(data.getMessage()); } } @@ -110,4 +133,5 @@ public class StatusConsoleListener implements StatusListener { this.stream.close(); } } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java index 32accf9b21..32ad1d1184 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java @@ -75,14 +75,19 @@ public final class StatusLogger extends AbstractLogger { private static final String NOT_AVAIL = "?"; - private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties"); + static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties"); private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200); private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL); + static final String DATE_FORMAT = PROPS.getStringProperty(STATUS_DATE_FORMAT); + + static final boolean DATE_FORMAT_PROVIDED = Strings.isNotBlank(DATE_FORMAT); + // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks. - private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(), + private static final StatusLogger STATUS_LOGGER = new StatusLogger( + StatusLogger.class.getName(), ParameterizedNoReferenceMessageFactory.INSTANCE); private final SimpleLogger logger; @@ -131,10 +136,8 @@ public final class StatusLogger extends AbstractLogger { */ private StatusLogger(final String name, final MessageFactory messageFactory) { super(name, messageFactory); - final String dateFormat = PROPS.getStringProperty(STATUS_DATE_FORMAT, Strings.EMPTY); - final boolean showDateTime = !Strings.isEmpty(dateFormat); final Level loggerLevel = isDebugPropertyEnabled() ? Level.TRACE : Level.ERROR; - this.logger = new SimpleLogger("StatusLogger", loggerLevel, false, true, showDateTime, false, dateFormat, messageFactory, PROPS, System.err); + this.logger = new SimpleLogger("StatusLogger", loggerLevel, false, true, DATE_FORMAT_PROVIDED, false, DATE_FORMAT, messageFactory, PROPS, System.err); this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 49c2090af1..c13af687d7 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -30,6 +30,9 @@ - "remove" - Removed --> <release version="2.19.0" date="2022-09-09" description="GA Release 2.19.0"> + <action issue="LOG4J2-3584" dev="vy" type="fix"> + Make StatusConsoleListener use SimpleLogger internally. + </action> <action issue="LOG4J2-3614" dev="vy" type="fix" due-to="strainu"> Harden InstantFormatter against delegate failures. </action>
