You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2022/12/30 01:38:00 UTC

[logging-log4j2] branch release-2.x updated: LOG4J2-2785 - Allow logger name to be abbreviated to the right-most n words

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

rgoers pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/release-2.x by this push:
     new 2a0c028000 LOG4J2-2785 - Allow logger name to be abbreviated to the right-most n words
2a0c028000 is described below

commit 2a0c02800044b970063a2c08dacdd61f9368e5b5
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Thu Dec 29 18:37:47 2022 -0700

    LOG4J2-2785 - Allow logger name to be abbreviated to the right-most n words
---
 .../core/pattern/DynamicWordAbbreviatorTest.java   |  75 +++++++++++++
 .../log4j/core/pattern/NameAbbreviatorTest.java    |   7 +-
 .../log4j/core/pattern/DynamicWordAbbreviator.java | 118 +++++++++++++++++++++
 .../log4j/core/pattern/NameAbbreviator.java        |  21 +++-
 log4j-layout-template-json-test/pom.xml            |   1 +
 pom.xml                                            |   2 +
 src/changes/changes.xml                            |   5 +-
 src/site/xdoc/manual/layouts.xml.vm                |  25 ++++-
 8 files changed, 248 insertions(+), 6 deletions(-)

diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.java
new file mode 100644
index 0000000000..e52d20088f
--- /dev/null
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Unit tests for the {@link DynamicWordAbbreviator} class.
+ */
+class DynamicWordAbbreviatorTest extends Assertions {
+
+    @Test
+    void testNullAndEmptyInputs() {
+        DynamicWordAbbreviator abbreviator = DynamicWordAbbreviator.create("1.1*");
+
+        assertDoesNotThrow(() -> abbreviator.abbreviate("orig", null));
+        assertDoesNotThrow(() -> abbreviator.abbreviate(null, new StringBuilder()));
+
+        StringBuilder dest = new StringBuilder();
+        abbreviator.abbreviate(null, dest);
+        assertEquals("", dest.toString());
+
+        abbreviator.abbreviate("", dest);
+        assertEquals("", dest.toString());
+    }
+
+    @ParameterizedTest(name = "[{index}] \"{0}\"")
+    @ValueSource(strings = {
+            "",
+            " ",
+            "0.0*",
+            "0,0*",
+            "1.2",
+            "1.2**",
+            "1.0*"
+    })
+    void testInvalidPatterns(String pattern) {
+        assertNull(DynamicWordAbbreviator.create(pattern));
+    }
+
+    @ParameterizedTest(name = "[{index}] \"{0}\" \"{1}\" \"{2}\"")
+    @CsvSource(delimiter = '|', value = {
+            "1.1*|.|.",
+            "1.1*|\\ |\\ ",
+            "1.1*|org.novice.o|o.n.o",
+            "1.1*|org.novice.|o.novice",
+            "1.1*|org......novice|o.novice",
+            "1.1*|org. . .novice|o. . .novice",
+    })
+    void testStrangeWords(String pattern, String input, String expected) {
+        DynamicWordAbbreviator abbreviator = DynamicWordAbbreviator.create(pattern);
+        StringBuilder actual = new StringBuilder();
+        abbreviator.abbreviate(input, actual);
+        assertEquals(expected, actual.toString());
+    }
+
+}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java
index d49a6f39a2..5444c40a62 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java
@@ -26,7 +26,7 @@ import org.junit.runners.Parameterized;
 import static org.junit.Assert.*;
 
 /**
- *
+ *  Unit tests for {@link NameAbbreviator} which abbreviates dot-delimited strings such as logger and class names.
  */
 @RunWith(Parameterized.class)
 public class NameAbbreviatorTest {
@@ -51,7 +51,10 @@ public class NameAbbreviatorTest {
                 { "1.", "o.a.l.l.c.p.NameAbbreviatorTest" },
                 { "1.1.~", "o.a.~.~.~.~.NameAbbreviatorTest" },
                 { "1.1.1.*", "o.a.l.log4j.core.pattern.NameAbbreviatorTest" },
-                { ".", "......NameAbbreviatorTest" }
+                { ".", "......NameAbbreviatorTest" },
+                { "1.2*", "o.a.l.l.c.pattern.NameAbbreviatorTest" },
+                { "1.3*", "o.a.l.l.core.pattern.NameAbbreviatorTest" },
+                { "1.8*", "org.apache.logging.log4j.core.pattern.NameAbbreviatorTest" }
             }
         );
     }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviator.java
new file mode 100644
index 0000000000..305731e93a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviator.java
@@ -0,0 +1,118 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>Specialized abbreviator that shortens all words to the first char except the indicated number of rightmost words.
+ * To select this abbreviator, use pattern <code>1.n*</code> where n (&gt; 0) is the number of rightmost words to leave unchanged.</p>
+ *
+ * By example for input <code>org.apache.logging.log4j.core.pattern.NameAbbreviator</code>:<br>
+ * <pre>
+ * 1.1*     =&gt;   o.a.l.l.c.p.NameAbbreviator
+ * 1.2*     =&gt;   o.a.l.l.c.pattern.NameAbbreviator
+ * 1.3*     =&gt;   o.a.l.l.core.pattern.NameAbbreviator
+ * ..
+ * 1.999*   =&gt;   org.apache.logging.log4j.core.pattern.NameAbbreviator
+ * </pre>
+ * @since 2.19.1
+ */
+class DynamicWordAbbreviator extends NameAbbreviator {
+
+    /** Right-most number of words (at least one) that will not be abbreviated. */
+    private final int rightWordCount;
+
+    static DynamicWordAbbreviator create(String pattern) {
+        if (pattern != null) {
+            Matcher matcher = Pattern.compile("1\\.([1-9][0-9]*)\\*").matcher(pattern);
+            if (matcher.matches()) {
+                return new DynamicWordAbbreviator(Integer.parseInt(matcher.group(1)));
+            }
+        }
+        return null;
+    }
+
+    private DynamicWordAbbreviator(int rightWordCount) {
+        this.rightWordCount = rightWordCount;
+    }
+
+    @Override
+    public void abbreviate(final String original, final StringBuilder destination) {
+        if (original == null || destination == null) {
+            return;
+        }
+
+        // for efficiency refrain from using String#split or StringTokenizer
+        final String[] words = split(original, '.');
+        final int wordCount = words.length;
+
+        if (rightWordCount >= wordCount) {
+            // nothing to abbreviate
+            destination.append(original);
+            return;
+        }
+
+        final int lastAbbrevIdx = wordCount - rightWordCount; // last index to abbreviate
+        for (int i = 0; i < wordCount; i++) {
+            if (i >= lastAbbrevIdx) {
+                destination.append(words[i]);
+                if (i < wordCount - 1) {
+                    destination.append(".");
+                }
+            } else if (words[i].length() > 0) {
+                destination.append(words[i].charAt(0))
+                        .append(".");
+            }
+        }
+    }
+
+    static String[] split(final String input, final char delim) {
+        if (input == null) {
+            return null;
+        } else if (input.isEmpty()) {
+            return new String[0];
+        }
+
+        int countDelim = input.chars().filter(c -> c == delim).map(c -> 1).sum();
+        final String[] tokens = new String[countDelim + 1];
+
+        int countToken = 0;
+        int idxBegin = 0;
+        int idxDelim = 0;
+
+        while ((idxDelim = input.indexOf(delim, idxBegin)) > -1) {
+            if (idxBegin < idxDelim) {
+                tokens[countToken++] = input.substring(idxBegin, idxDelim);
+            }
+            idxBegin = idxDelim + 1;
+        }
+
+        if (idxBegin < input.length()) { // remains
+            tokens[countToken++] = input.substring(idxBegin);
+        }
+
+        if (countToken < tokens.length) {
+            return Arrays.copyOf(tokens, countToken);
+        }
+
+        return tokens;
+    }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java
index d6f4e4d4d6..b4217d1be4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.core.pattern;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.logging.log4j.core.util.Integers;
@@ -56,6 +57,11 @@ public abstract class NameAbbreviator {
                 return DEFAULT;
             }
 
+            NameAbbreviator dwa = DynamicWordAbbreviator.create(trimmed);
+            if (dwa != null) {
+                return dwa;
+            }
+
             boolean isNegativeNumber;
             final String number;
 
@@ -83,7 +89,7 @@ public abstract class NameAbbreviator {
                         isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN);
             }
 
-            final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
+            final List<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
             char ellipsis;
             int charCount;
             int pos = 0;
@@ -259,7 +265,7 @@ public abstract class NameAbbreviator {
      * Fragment of an pattern abbreviator.
      */
     private static final class PatternAbbreviatorFragment {
-        
+
         static final PatternAbbreviatorFragment[] EMPTY_ARRAY = {};
 
         /**
@@ -318,6 +324,12 @@ public abstract class NameAbbreviator {
             }
             return nextDot + 1;
         }
+
+        @Override
+        public String toString() {
+            return String.format("%s[charCount=%s, ellipsis=%s]",
+                    getClass().getSimpleName(), charCount, Integer.toHexString(ellipsis));
+        }
     }
 
     /**
@@ -363,5 +375,10 @@ public abstract class NameAbbreviator {
         PatternAbbreviatorFragment fragment(int index) {
             return fragments[Math.min(index, fragments.length - 1)];
         }
+
+        @Override
+        public String toString() {
+            return String.format("%s[fragments=%s]", getClass().getSimpleName(), Arrays.toString(fragments));
+        }
     }
 }
diff --git a/log4j-layout-template-json-test/pom.xml b/log4j-layout-template-json-test/pom.xml
index d180665160..8d6f8e40e5 100644
--- a/log4j-layout-template-json-test/pom.xml
+++ b/log4j-layout-template-json-test/pom.xml
@@ -37,6 +37,7 @@
     <projectDir>/log4j-layout-template-json</projectDir>
     <module.name>org.apache.logging.log4j.layout.template.json</module.name>
     <maven.doap.skip>true</maven.doap.skip>
+    <javadoc.skip>true</javadoc.skip>
   </properties>
 
   <dependencies>
diff --git a/pom.xml b/pom.xml
index 1d70a46ca0..fb864aab2c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -336,6 +336,7 @@
     <javax-servlet-jsp.version>2.3.3</javax-servlet-jsp.version>
     <jansi.version>2.4.0</jansi.version>
     <java-allocation-instrumenter.version>3.3.0</java-allocation-instrumenter.version>
+    <javadoc.skip>false</javadoc.skip>
     <jconsole.version>1.7.0</jconsole.version>
     <jctools.version>3.3.0</jctools.version>
     <je.version>18.3.12</je.version>
@@ -1228,6 +1229,7 @@
             Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
             and the Apache Log4j logo are trademarks of The Apache Software Foundation.&lt;/p&gt;</bottom>
             <doclint>none</doclint>
+            <skip>${javadoc.skip}</skip>
           </configuration>
         </plugin>
         <plugin>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b94de7cdb5..8b67810da1 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,7 +30,10 @@
          - "remove" - Removed
     -->
     <release version="2.19.1" date="TBD" description="GA Release 2.19.1">
-      <action issue="LOG4J2-3487" dev="dmessink" type="fix" due-to="Dave Messink">
+      <action issue="LOG4J2-2785" dev="rgoers" type="add" due-to="Markus Spann">
+        Pattern Layout to abbreviate the name of all logger components except the 2 rightmost.
+      </action>
+      <action issue="LOG4J2-3487" dev="rgoers" type="fix" due-to="Dave Messink">
         Correct default to not include location for AsyncRootLoggers
       </action>
       <action issue="LOG4J2-2678" dev="pkarwasz" type="update" due-to="Federico D'Ambrosio">
diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm
index 5c99a4d1c0..08e5a5b1b6 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -855,7 +855,10 @@ WARN  [main]: Message 2</pre>
                 <p>When the precision specifier is an integer value, it reduces the size of the logger name.
                   If the number is positive, the layout prints the corresponding number of rightmost logger
                   name components. If negative, the layout removes the corresponding number of leftmost logger
-                  name components.
+                  name components. If the precision contains periods then the number before the first period
+                  identifies the length to be printed from items that precede tokens in the rest of the pattern.
+                  If the number after the first period is followed by an asterisk it indicates how many of the
+                  rightmost tokens will be printed in full. See the table below for abbreviation examples.
                 </p>
                 <p>
                    If the precision contains any non-integer characters, then the layout abbreviates the name
@@ -913,6 +916,26 @@ WARN  [main]: Message 2</pre>
                     <td>org.apache.${break}commons.test.${break}Foo</td>
                     <td>....Foo</td>
                   </tr>
+                  <tr>
+                    <td>%c{1.1.1.*}</td>
+                    <td>org.apache.${break}commons.test.${break}Foo</td>
+                    <td>o.a.c.test.Foo</td>
+                  </tr>
+                  <tr>
+                    <td>%c{1.2.*}</td>
+                    <td>org.apache.${break}commons.test.${break}Foo</td>
+                    <td>o.a.c.test.Foo</td>
+                  </tr>
+                  <tr>
+                    <td>%c{1.3.*}</td>
+                    <td>org.apache.${break}commons.test.${break}Foo</td>
+                    <td>o.a.commons.test.Foo</td>
+                  </tr>
+                  <tr>
+                    <td>%c{1.8.*}</td>
+                    <td>org.apache.${break}commons.test.${break}Foo</td>
+                    <td>org.apache.commons.test.Foo</td>
+                  </tr>
                 </table>
               </td>
             </tr>