You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2022/10/10 20:33:56 UTC

[logging-log4j2] 01/01: LOG4J2-3584 Make StatusConsoleListener use SimpleLogger internally.

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ı <vo...@yazi.ci>
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>