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 2019/06/20 14:50:05 UTC

[logging-log4j2] 01/02: [LOG4J2-2403] Allow zero padding the counter of a RollingFileAppender

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

commit 420c737dd701ddd644fed3ba4447ec74696aad0d
Author: Marco Herrn <ma...@mherrn.de>
AuthorDate: Tue Oct 23 14:44:03 2018 +0200

    [LOG4J2-2403] Allow zero padding the counter of a RollingFileAppender
    
    - Introduce a pattern for zero-padding (e.g. %03i) in file names
    - This pattern also works for log patterns (e.g. %08sn)
---
 .../appender/rolling/AbstractRolloverStrategy.java |  4 +-
 .../logging/log4j/core/pattern/FormattingInfo.java | 48 +++++++++++++++-
 .../logging/log4j/core/pattern/PatternParser.java  | 18 ++++--
 .../appender/rolling/RolloverFilePatternTest.java  | 59 +++++++++++++++++++
 .../appender/rolling/RolloverWithPaddingTest.java  | 67 ++++++++++++++++++++++
 .../SequenceNumberPatternConverterTest.java        | 25 +++++++-
 .../SequenceNumberPatternConverterTest.yml         | 13 +++--
 .../test/resources/log4j-rolling-with-padding.xml  | 42 ++++++++++++++
 8 files changed, 261 insertions(+), 15 deletions(-)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java
index 741afb8..a69e220 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java
@@ -46,6 +46,8 @@ public abstract class AbstractRolloverStrategy implements RolloverStrategy {
      */
     protected static final Logger LOGGER = StatusLogger.getLogger();
 
+    public static final Pattern PATTERN_COUNTER= Pattern.compile(".*%((?<ZEROPAD>0)?(?<PADDING>\\d+))?i.*");
+
     protected final StrSubstitutor strSubstitutor;
 
     protected AbstractRolloverStrategy(final StrSubstitutor strSubstitutor) {
@@ -105,7 +107,7 @@ public abstract class AbstractRolloverStrategy implements RolloverStrategy {
         } else {
             parent.mkdirs();
         }
-        if (!logfilePattern.contains("%i")) {
+        if (!PATTERN_COUNTER.matcher(logfilePattern).matches()) {
             return eligibleFiles;
         }
         final Path dir = parent.toPath();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java
index 0b9f2b9..bd88679 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java
@@ -30,6 +30,11 @@ public final class FormattingInfo {
     private static final char[] SPACES = new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
 
     /**
+     * Array of zeros.
+     */
+    private static final char[] ZEROS = new char[] { '0', '0', '0', '0', '0', '0', '0', '0' };
+
+    /**
      * Default instance.
      */
     private static final FormattingInfo DEFAULT = new FormattingInfo(false, 0, Integer.MAX_VALUE, true);
@@ -55,6 +60,11 @@ public final class FormattingInfo {
     private final boolean leftTruncate;
 
     /**
+     * Use zero-padding instead whitespace padding
+     */
+    private final boolean zeroPad;
+
+    /**
      * Creates new instance.
      *
      * @param leftAlign
@@ -67,10 +77,29 @@ public final class FormattingInfo {
      *            truncates to the left if true
      */
     public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength, final boolean leftTruncate) {
+        this(leftAlign, minLength, maxLength, leftTruncate, false);
+    }
+
+    /**
+     * Creates new instance.
+     *
+     * @param leftAlign
+     *            left align if true.
+     * @param minLength
+     *            minimum length.
+     * @param maxLength
+     *            maximum length.
+     * @param leftTruncate
+     *            truncates to the left if true
+     * @param zeroPad
+     *            use zero-padding instead of whitespace-padding
+     */
+    public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength, final boolean leftTruncate, final boolean zeroPad) {
         this.leftAlign = leftAlign;
         this.minLength = minLength;
         this.maxLength = maxLength;
         this.leftTruncate = leftTruncate;
+        this.zeroPad = zeroPad;
     }
 
     /**
@@ -101,6 +130,15 @@ public final class FormattingInfo {
 	}
 
     /**
+     * Determine if zero-padded.
+     *
+     * @return true if zero-padded.
+     */
+    public boolean isZeroPad() {
+        return zeroPad;
+    }
+
+    /**
      * Get minimum length.
      *
      * @return minimum length.
@@ -146,11 +184,13 @@ public final class FormattingInfo {
             } else {
                 int padLength = minLength - rawLength;
 
-                for (; padLength > SPACES.length; padLength -= SPACES.length) {
-                    buffer.insert(fieldStart, SPACES);
+                final char[] paddingArray= zeroPad ? ZEROS : SPACES;
+
+                for (; padLength > paddingArray.length; padLength -= paddingArray.length) {
+                    buffer.insert(fieldStart, paddingArray);
                 }
 
-                buffer.insert(fieldStart, SPACES, 0, padLength);
+                buffer.insert(fieldStart, paddingArray, 0, padLength);
             }
         }
     }
@@ -172,6 +212,8 @@ public final class FormattingInfo {
         sb.append(minLength);
         sb.append(", leftTruncate=");
         sb.append(leftTruncate);
+        sb.append(", zeroPad=");
+        sb.append(zeroPad);
         sb.append(']');
         return sb.toString();
     }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
index 2e854e9..96aa819 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
@@ -396,9 +396,15 @@ public final class PatternParser {
                 currentLiteral.append(c);
 
                 switch (c) {
+                case '0':
+                    // a '0' directly after the % sign indicates zero-padding
+                    formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
+                            formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), true);
+                    break;
+
                 case '-':
                     formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(),
-                            formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
+                            formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
                     break;
 
                 case '.':
@@ -409,7 +415,7 @@ public final class PatternParser {
 
                     if (c >= '0' && c <= '9') {
                         formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0',
-                                formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
+                                formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
                         state = ParserState.MIN_STATE;
                     } else {
                         i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
@@ -430,7 +436,7 @@ public final class PatternParser {
                 if (c >= '0' && c <= '9') {
                     // Multiply the existing value and add the value of the number just encountered.
                     formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength()
-                            * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
+                            * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
                 } else if (c == '.') {
                     state = ParserState.DOT_STATE;
                 } else {
@@ -448,14 +454,14 @@ public final class PatternParser {
                 switch (c) {
                 case '-':
                     formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
-                            formattingInfo.getMaxLength(),false);
+                            formattingInfo.getMaxLength(),false, formattingInfo.isZeroPad());
                     break;
 
                 default:
 
 	                if (c >= '0' && c <= '9') {
 	                    formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
-	                            c - '0', formattingInfo.isLeftTruncate());
+	                            c - '0', formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
 	                    state = ParserState.MAX_STATE;
 	                } else {
 	                    LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
@@ -473,7 +479,7 @@ public final class PatternParser {
                 if (c >= '0' && c <= '9') {
                     // Multiply the existing value and add the value of the number just encountered.
                     formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
-                            formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate());
+                            formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
                 } else {
                     i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
                             patternConverters, formattingInfos, disableAnsi, noConsoleNoAnsi, convertBackslashes);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java
new file mode 100644
index 0000000..7d7ba91
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.appender.rolling;
+
+import java.util.regex.Matcher;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test getEligibleFiles method.
+ */
+public class RolloverFilePatternTest {
+
+    @Test
+    public void testFilePatternWithoutPadding() throws Exception {
+      final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%i.log.gz");
+      assertTrue(matcher.matches());
+      assertNull(matcher.group("ZEROPAD"));
+      assertNull(matcher.group("PADDING"));
+    }
+
+    @Test
+    public void testFilePatternWithSpacePadding() throws Exception {
+      final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%3i.log.gz");
+      assertTrue(matcher.matches());
+      assertNull(matcher.group("ZEROPAD"));
+      assertEquals("3", matcher.group("PADDING"));
+    }
+
+    @Test
+    public void testFilePatternWithZeroPadding() throws Exception {
+      final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%03i.log.gz");
+      assertTrue(matcher.matches());
+      assertEquals("0", matcher.group("ZEROPAD"));
+      assertEquals("3", matcher.group("PADDING"));
+    }
+
+    @Test
+    public void testFilePatternUnmatched() throws Exception {
+      final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%n.log.gz");
+      assertFalse(matcher.matches());
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java
new file mode 100644
index 0000000..7b0fb22
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.appender.rolling;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+/**
+ * Tests that zero-padding in rolled files works correctly.
+ */
+public class RolloverWithPaddingTest {
+  private static final String CONFIG = "log4j-rolling-with-padding.xml";
+  private static final String DIR = "target/rolling-with-padding";
+
+  private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
+
+  @Rule
+  public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR);
+
+  @Test
+  public void testAppender() throws Exception {
+    final Logger logger = loggerContextRule.getLogger();
+    for (int i = 0; i < 10; ++i) {
+      // 30 chars per message: each message triggers a rollover
+      logger.fatal("This is a test message number " + i); // 30 chars:
+    }
+    Thread.sleep(100); // Allow time for rollover to complete
+
+    final File dir = new File(DIR);
+    assertTrue("Dir " + DIR + " should exist", dir.exists());
+    assertTrue("Dir " + DIR + " should contain files", dir.listFiles().length == 6);
+
+    final File[] files = dir.listFiles();
+    final List<String> expected = Arrays.asList("rollingtest.log", "test-001.log", "test-002.log", "test-003.log", "test-004.log", "test-005.log");
+    assertEquals("Unexpected number of files", expected.size(), files.length);
+    for (final File file : files) {
+      if (!expected.contains(file.getName())) {
+        fail("unexpected file" + file);
+      }
+    }
+  }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java
index b3fe206..4043e1d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java
@@ -21,11 +21,12 @@ import java.util.List;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.junit.LoggerContextRule;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.ClassRule;
 import org.junit.Test;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.ClassRule;
 
 /**
  *
@@ -35,6 +36,12 @@ public class SequenceNumberPatternConverterTest {
     @ClassRule
     public static LoggerContextRule ctx = new LoggerContextRule("SequenceNumberPatternConverterTest.yml");
 
+    @Before
+    public void before() {
+      ctx.getListAppender("List").clear();
+      ctx.getListAppender("Padded").clear();
+    }
+
     @Test
     public void testSequenceIncreases() throws Exception {
         final Logger logger = ctx.getLogger();
@@ -46,6 +53,22 @@ public class SequenceNumberPatternConverterTest {
 
         final ListAppender app = ctx.getListAppender("List");
         final List<String> messages = app.getMessages();
+        System.out.println("Written messages: "+messages);
         assertThat(messages, contains("1", "2", "3", "4", "5"));
     }
+
+    @Test
+    public void testPaddedSequence() throws Exception {
+        final Logger logger = ctx.getLogger();
+        logger.info("Message 1");
+        logger.info("Message 2");
+        logger.info("Message 3");
+        logger.info("Message 4");
+        logger.info("Message 5");
+
+        final ListAppender app = ctx.getListAppender("Padded");
+        final List<String> messages = app.getMessages();
+        System.out.println("Written messages "+messages);
+        assertThat(messages, contains("001", "002", "003", "004", "005"));
+    }
 }
diff --git a/log4j-core/src/test/resources/SequenceNumberPatternConverterTest.yml b/log4j-core/src/test/resources/SequenceNumberPatternConverterTest.yml
index 05f0b05..64698cf 100644
--- a/log4j-core/src/test/resources/SequenceNumberPatternConverterTest.yml
+++ b/log4j-core/src/test/resources/SequenceNumberPatternConverterTest.yml
@@ -4,12 +4,17 @@ Configuration:
 
   Appenders:
     List:
-      name: List
-      PatternLayout:
-        pattern: '%sn'
+      - name: List
+        PatternLayout:
+          pattern: '%sn'
+
+      - name: Padded
+        PatternLayout:
+          pattern: '%03sn'
 
   Loggers:
     Root:
       level: INFO
       AppenderRef:
-        ref: List
+        - ref: List
+        - ref: Padded
diff --git a/log4j-core/src/test/resources/log4j-rolling-with-padding.xml b/log4j-core/src/test/resources/log4j-rolling-with-padding.xml
new file mode 100644
index 0000000..4bb8d20
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-with-padding.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<Configuration status="WARN" name="RollingWithPadding">
+  <Properties>
+    <Property name="base">target/rolling-with-padding/</Property>
+  </Properties>
+
+  <Appenders>
+    <RollingFile name="RollingFile" fileName="${base}/rollingtest.log" filePattern="${base}/test-%03i.log">
+      <PatternLayout>
+        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
+      </PatternLayout>
+      <Policies>
+        <SizeBasedTriggeringPolicy size="30" />
+      </Policies>
+      <DefaultRolloverStrategy max="5" />
+    </RollingFile>
+  </Appenders>
+
+  <Loggers>
+    <Root level="trace">
+      <AppenderRef ref="RollingFile" />
+    </Root>
+  </Loggers>
+
+</Configuration>