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

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
     new a03cabf3a3 Add escaping to logging output
a03cabf3a3 is described below

commit a03cabf3a36a42d27d8d997ed31f034f50ba6cd5
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Aug 18 22:20:00 2025 +0100

    Add escaping to logging output
    
    This aligns the other formatters with the JSON formatter
---
 java/org/apache/juli/JdkLoggerFormatter.java |  4 +-
 java/org/apache/juli/LogUtil.java            | 64 +++++++++++++++++++
 java/org/apache/juli/OneLineFormatter.java   |  4 +-
 java/org/apache/juli/VerbatimFormatter.java  |  7 +--
 test/org/apache/juli/TestLogUtil.java        | 93 ++++++++++++++++++++++++++++
 webapps/docs/changelog.xml                   |  3 +
 6 files changed, 167 insertions(+), 8 deletions(-)

diff --git a/java/org/apache/juli/JdkLoggerFormatter.java 
b/java/org/apache/juli/JdkLoggerFormatter.java
index 80ba904c81..65d44a491d 100644
--- a/java/org/apache/juli/JdkLoggerFormatter.java
+++ b/java/org/apache/juli/JdkLoggerFormatter.java
@@ -100,7 +100,7 @@ public class JdkLoggerFormatter extends Formatter {
         }
 
         // Append the message
-        buf.append(message);
+        buf.append(LogUtil.escape(message));
 
         // Append stack trace if not null
         if (t != null) {
@@ -110,7 +110,7 @@ public class JdkLoggerFormatter extends Formatter {
             java.io.PrintWriter pw = new java.io.PrintWriter(sw);
             t.printStackTrace(pw);
             pw.close();
-            buf.append(sw);
+            buf.append(LogUtil.escape(sw.toString()));
         }
 
         buf.append(System.lineSeparator());
diff --git a/java/org/apache/juli/LogUtil.java 
b/java/org/apache/juli/LogUtil.java
new file mode 100644
index 0000000000..c7eb098331
--- /dev/null
+++ b/java/org/apache/juli/LogUtil.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+public class LogUtil {
+
+    private LogUtil() {
+        // Utility class. Hide default constructor
+    }
+
+
+    /**
+     * Escape a string so it can be displayed in a readable format. Characters 
that may not be printable in some/all of
+     * the contexts in which log messages will be viewed will be escaped using 
Java \\uNNNN escaping.
+     * <p>
+     * All control characters are escaped apart from horizontal tab (\\u0009), 
new line (\\u000a) and carriage return
+     * (\\u000d).
+     *
+     * @param input The string to escape
+     *
+     * @return The escaped form of the input string
+     */
+    @SuppressWarnings("null") // sb is not null when used
+    public static String escape(final String input) {
+        final int len = input.length();
+        int i = 0;
+        int lastControl = -1;
+        StringBuilder sb = null;
+        while (i < len) {
+            char c = input.charAt(i);
+            if (Character.getType(c) == Character.CONTROL) {
+                if (!(c == '\t' || c == '\n' || c == '\r')) {
+                    if (lastControl == -1) {
+                        sb = new StringBuilder(len + 20);
+                    }
+                    sb.append(input.substring(lastControl + 1, i));
+                    sb.append(String.format("\\u%1$04x", Integer.valueOf(c)));
+                    lastControl = i;
+                }
+            }
+            i++;
+        }
+        if (lastControl == -1) {
+            return input;
+        } else {
+            sb.append(input.substring(lastControl + 1, len));
+            return sb.toString();
+        }
+    }
+}
diff --git a/java/org/apache/juli/OneLineFormatter.java 
b/java/org/apache/juli/OneLineFormatter.java
index 4ad3680b18..3c54de43af 100644
--- a/java/org/apache/juli/OneLineFormatter.java
+++ b/java/org/apache/juli/OneLineFormatter.java
@@ -147,7 +147,7 @@ public class OneLineFormatter extends Formatter {
 
         // Message
         sb.append(' ');
-        sb.append(formatMessage(record));
+        sb.append(LogUtil.escape(formatMessage(record)));
 
         // New line for next record
         sb.append(System.lineSeparator());
@@ -158,7 +158,7 @@ public class OneLineFormatter extends Formatter {
             PrintWriter pw = new IndentingPrintWriter(sw);
             record.getThrown().printStackTrace(pw);
             pw.close();
-            sb.append(sw.getBuffer());
+            sb.append(LogUtil.escape(sw.toString()));
         }
 
         return sb.toString();
diff --git a/java/org/apache/juli/VerbatimFormatter.java 
b/java/org/apache/juli/VerbatimFormatter.java
index 88efa4ddf7..2653b180cf 100644
--- a/java/org/apache/juli/VerbatimFormatter.java
+++ b/java/org/apache/juli/VerbatimFormatter.java
@@ -20,9 +20,9 @@ import java.util.logging.Formatter;
 import java.util.logging.LogRecord;
 
 /**
- * Outputs just the log message with no additional elements. Stack traces are 
not logged. Log messages are separated by
- * <code>System.lineSeparator()</code>. This is intended for use by access 
logs and the like that need complete control
- * over the output format.
+ * Outputs just the log message with no additional elements and no escaping. 
Stack traces are not logged. Log messages
+ * are separated by <code>System.lineSeparator()</code>. This is intended for 
use by access logs and the like that need
+ * complete control over the output format.
  */
 public class VerbatimFormatter extends Formatter {
 
@@ -31,5 +31,4 @@ public class VerbatimFormatter extends Formatter {
         // Timestamp + New line for next record
         return record.getMessage() + System.lineSeparator();
     }
-
 }
diff --git a/test/org/apache/juli/TestLogUtil.java 
b/test/org/apache/juli/TestLogUtil.java
new file mode 100644
index 0000000000..12360c2212
--- /dev/null
+++ b/test/org/apache/juli/TestLogUtil.java
@@ -0,0 +1,93 @@
+/*
+ * 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 org.junit.Assert;
+import org.junit.Test;
+
+public class TestLogUtil {
+
+    @Test
+    public void testEscapeForLoggingEmptyString() {
+        doTestEscapeForLogging("");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingNone() {
+        doTestEscapeForLogging("No escaping");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlStart() {
+        doTestEscapeForLogging("\u0006Text", "\\u0006Text");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlMiddle() {
+        doTestEscapeForLogging("Text\u0006Text", "Text\\u0006Text");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlEnd() {
+        doTestEscapeForLogging("Text\u0006", "Text\\u0006");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlOnly() {
+        doTestEscapeForLogging("\u0006", "\\u0006");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlsStart() {
+        doTestEscapeForLogging("\u0006\u0007Text", "\\u0006\\u0007Text");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlsMiddle() {
+        doTestEscapeForLogging("Text\u0006\u0007Text", 
"Text\\u0006\\u0007Text");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlsEnd() {
+        doTestEscapeForLogging("Text\u0006\u0007", "Text\\u0006\\u0007");
+    }
+
+
+    @Test
+    public void testEscapeForLoggingControlsOnly() {
+        doTestEscapeForLogging("\u0006\u0007", "\\u0006\\u0007");
+    }
+
+
+    private void doTestEscapeForLogging(String input) {
+        doTestEscapeForLogging(input, input);
+    }
+
+
+    private void doTestEscapeForLogging(String input, String expected) {
+        String result = LogUtil.escape(input);
+        Assert.assertEquals(expected, result);
+    }
+}
\ No newline at end of file
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b89d1335d3..ce3aba451c 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -120,6 +120,9 @@
         by default rather then just the exception message when logging an error
         or warning in response to an exception. (markt)
       </scode>
+      <add>
+        Add escaping to log formatters to align with JSON formatter. (markt)
+      </add>
     </changelog>
   </subsection>
 </section>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to