This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 8a531b84435fdcf777e15b02516fa9564d4338cc
Author: opencode <[email protected]>
AuthorDate: Tue Oct 20 09:31:10 2026 +0200

    Avoid NPE in juli formatters for records with no message
    
    A LogRecord with a null message is legal, and Formatter.formatMessage()
    then returns null. The message was passed unguarded to LogUtil.escape()
    by OneLineFormatter and JdkLoggerFormatter and to
    JsonFormatter.JSONFilter.escape() by JsonFormatter, so all three
    formatters threw a NullPointerException instead of formatting the
    record. When used with a handler that catches the exception, such as
    FileHandler, the record was silently lost. The formatters are public
    API that can be used with any handler, where the exception propagated
    out of the logging call of the application.
    
    Both escaping utilities now handle a null input as an empty string and
    the handling is documented in their Javadoc. Records with no message
    are formatted with an empty message, consistently in all three
    formatters. Added regression tests for the two utilities and for each
    formatter.
---
 java/org/apache/juli/JsonFormatter.java          |  5 ++-
 java/org/apache/juli/LogUtil.java                |  5 ++-
 test/org/apache/juli/TestJdkLoggerFormatter.java | 45 ++++++++++++++++++++++++
 test/org/apache/juli/TestJsonFormatter.java      | 16 +++++++++
 test/org/apache/juli/TestLogUtil.java            |  6 ++++
 test/org/apache/juli/TestOneLineFormatter.java   | 44 +++++++++++++++++++++++
 6 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/juli/JsonFormatter.java 
b/java/org/apache/juli/JsonFormatter.java
index 7e4e50284d..33e3d03b65 100644
--- a/java/org/apache/juli/JsonFormatter.java
+++ b/java/org/apache/juli/JsonFormatter.java
@@ -139,11 +139,14 @@ public class JsonFormatter extends OneLineFormatter {
         /**
          * Escape the given string.
          *
-         * @param input the string
+         * @param input the string. A null value is handled as an empty string.
          *
          * @return the escaped string
          */
         public static String escape(String input) {
+            if (input == null) {
+                return "";
+            }
             return escape(input, 0, input.length()).toString();
         }
 
diff --git a/java/org/apache/juli/LogUtil.java 
b/java/org/apache/juli/LogUtil.java
index 6f14afc916..f0458baab6 100644
--- a/java/org/apache/juli/LogUtil.java
+++ b/java/org/apache/juli/LogUtil.java
@@ -33,12 +33,15 @@ public class LogUtil {
      * All control characters are escaped apart from horizontal tab (\\u0009), 
new line (\\u000a) and carriage return
      * (\\u000d).
      *
-     * @param input The string to escape
+     * @param input The string to escape. A null value is handled as an empty 
string.
      *
      * @return The escaped form of the input string
      */
     @SuppressWarnings("null") // sb is not null when used
     public static String escape(final String input) {
+        if (input == null) {
+            return "";
+        }
         final int len = input.length();
         int i = 0;
         int lastControl = -1;
diff --git a/test/org/apache/juli/TestJdkLoggerFormatter.java 
b/test/org/apache/juli/TestJdkLoggerFormatter.java
new file mode 100644
index 0000000000..06de6f8a00
--- /dev/null
+++ b/test/org/apache/juli/TestJdkLoggerFormatter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.juli;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestJdkLoggerFormatter {
+
+    /*
+     * A record with no message is legal. The formatter must render it, with 
the message as an empty string, rather
+     * than throw a NullPointerException.
+     */
+    @Test
+    public void testNullMessage() {
+        JdkLoggerFormatter formatter = new JdkLoggerFormatter();
+        LogRecord logRecord = new LogRecord(Level.FINE, null);
+        logRecord.setLoggerName("test");
+
+        String result = formatter.format(logRecord);
+
+        // With an empty message the line has nothing after the padding of the 
logger name, in particular the
+        // string "null" must not appear in place of the message
+        Assert.assertTrue("Unexpected result [" + result + "]",
+                result.endsWith(System.lineSeparator()));
+        Assert.assertFalse("Unexpected result [" + result + "]", 
result.contains("null"));
+    }
+}
diff --git a/test/org/apache/juli/TestJsonFormatter.java 
b/test/org/apache/juli/TestJsonFormatter.java
index c82f1c260e..8b52ee2afe 100644
--- a/test/org/apache/juli/TestJsonFormatter.java
+++ b/test/org/apache/juli/TestJsonFormatter.java
@@ -55,6 +55,22 @@ public class TestJsonFormatter {
         Assert.assertEquals(trace.get(0), "java.lang.IllegalStateException: 
Bad state");
     }
 
+    @Test
+    public void testNullMessage() throws Exception {
+
+        Formatter formatter = new JsonFormatter();
+        LogRecord logRecord = new LogRecord(Level.FINE, null);
+        logRecord.setLoggerName("test");
+
+        String result = formatter.format(logRecord);
+
+        // Verify JSON content
+        Assert.assertTrue(result.startsWith("{"));
+        JSONParser parser = new JSONParser(new StringReader(result));
+        LinkedHashMap<String,Object> json = parser.object();
+        Assert.assertEquals("", json.get("message"));
+    }
+
     @Test
     public void testCircularCause() throws Exception {
 
diff --git a/test/org/apache/juli/TestLogUtil.java 
b/test/org/apache/juli/TestLogUtil.java
index 12360c2212..7296092896 100644
--- a/test/org/apache/juli/TestLogUtil.java
+++ b/test/org/apache/juli/TestLogUtil.java
@@ -21,6 +21,12 @@ import org.junit.Test;
 
 public class TestLogUtil {
 
+    @Test
+    public void testEscapeForLoggingNull() {
+        doTestEscapeForLogging(null, "");
+    }
+
+
     @Test
     public void testEscapeForLoggingEmptyString() {
         doTestEscapeForLogging("");
diff --git a/test/org/apache/juli/TestOneLineFormatter.java 
b/test/org/apache/juli/TestOneLineFormatter.java
new file mode 100644
index 0000000000..2b093d8ed5
--- /dev/null
+++ b/test/org/apache/juli/TestOneLineFormatter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.juli;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestOneLineFormatter {
+
+    /*
+     * A record with no message is legal. The formatter must render it, with 
the message as an empty string, rather
+     * than throw a NullPointerException.
+     */
+    @Test
+    public void testNullMessage() {
+        OneLineFormatter formatter = new OneLineFormatter();
+        LogRecord logRecord = new LogRecord(Level.FINE, null);
+        logRecord.setLoggerName("test");
+
+        String result = formatter.format(logRecord);
+
+        // With an empty message the output ends with the separator between 
the source and the message followed by
+        // the line separator for the next record
+        Assert.assertTrue("Unexpected result [" + result + "]",
+                result.endsWith(" " + System.lineSeparator()));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to