You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2023/01/03 21:29:31 UTC

[logging-log4j2] 01/03: LOG4J2-1631 Honor timezone in file name pattern

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

pkarwasz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit c11a68769ff4733d5ece54d66575907e0a71d055
Author: Danas Mikelinskas <da...@mikelinskas.com>
AuthorDate: Fri Dec 30 13:04:16 2022 +0200

    LOG4J2-1631 Honor timezone in file name pattern
    
    Given pattern "app-%d{yyyy-MM-dd}{timezone}.log.gz"
    treat second pair of curly braces as timezone option.
    
    If timezone option is not set, then use default timezone as before.
    If timezone option is set, but it's parsing fails, then use GMT+0.
    (same DatePatternConverter behavior as in other places)
---
 .../appender/rolling/PatternProcessorTest.java     | 200 ++++++++++++++++++++-
 .../core/appender/rolling/PatternProcessor.java    |  36 ++--
 .../log4j/core/pattern/DatePatternConverter.java   |  22 +++
 .../core/pattern/FileDatePatternConverter.java     |   7 +
 4 files changed, 249 insertions(+), 16 deletions(-)

diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java
index 4ba1bc1e92..c03b27851b 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java
@@ -14,13 +14,13 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 package org.apache.logging.log4j.core.appender.rolling;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
+import java.util.TimeZone;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.parallel.ResourceAccessMode;
@@ -82,6 +82,42 @@ public class PatternProcessorTest {
         assertEquals(format(expected.getTimeInMillis()), format(actual));
     }
 
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHourDstStart() {
+        // America/Chicago 2014 - DST start - Mar 9 02:00
+        // during winter GMT-6
+        // during summer GMT-5
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        initial.set(2014, Calendar.MARCH, 9, 1, 31, 59); // Tue, March 9, 2014, 01:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Wed, March 9, 2014, 02:00
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        expected.set(2014, Calendar.MARCH, 9, 2, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeHourlyReturnsFirstMinuteOfHourAfterNextHourDstEnd() {
+        // America/Chicago 2014 - DST end - Nov 2 02:00
+        // during summer GMT-5
+        // during winter GMT-6
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-5"));
+        initial.set(2014, Calendar.NOVEMBER, 2, 1, 31, 59); // Sun, November 2, 2014, 01:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Sun, November 2, 2014, 03:00 (i.e. 1h 29min since initial)
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-5"));
+        expected.set(2014, Calendar.NOVEMBER, 2, 3, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeHourlyReturnsFirstMinuteOfNextYear() {
@@ -274,4 +310,166 @@ public class PatternProcessorTest {
             Locale.setDefault(old);
         }
     }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDay() {
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}.log.gz");
+        final Calendar initial = Calendar.getInstance();
+        initial.set(2014, Calendar.MARCH, 4, 2, 31, 59); // Tue, March 4, 2014, 02:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Wed, March 5, 2014, 00:00
+        final Calendar expected = Calendar.getInstance();
+        expected.set(2014, Calendar.MARCH, 5, 0, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption1() {
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        initial.set(2014, Calendar.MARCH, 4, 2, 31, 59); // Tue, March 4, 2014, 02:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Wed, March 5, 2014, 00:00
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        expected.set(2014, Calendar.MARCH, 5, 0, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    @ResourceLock(value = Resources.TIME_ZONE)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption2() {
+        TimeZone old = TimeZone.getDefault();
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT+10")); // default is ignored if pattern contains timezone
+        try {
+            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
+            final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+            initial.set(2014, Calendar.MARCH, 4, 2, 31, 59); // Tue, March 4, 2014, 02:31
+            final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+            // expect Wed, March 5, 2014, 00:00
+            final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+            expected.set(2014, Calendar.MARCH, 5, 0, 0, 0);
+            expected.set(Calendar.MILLISECOND, 0);
+            assertEquals(format(expected.getTimeInMillis()), format(actual));
+        } finally {
+            TimeZone.setDefault(old);
+        }
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    @ResourceLock(value = Resources.TIME_ZONE)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption3() {
+        TimeZone old = TimeZone.getDefault();
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT-10")); // default is ignored if pattern contains timezone
+        try {
+            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
+            final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+            initial.set(2014, Calendar.MARCH, 4, 2, 31, 59); // Tue, March 4, 2014, 02:31
+            final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+            // expect Wed, March 5, 2014, 00:00
+            final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+            expected.set(2014, Calendar.MARCH, 5, 0, 0, 0);
+            expected.set(Calendar.MILLISECOND, 0);
+            assertEquals(format(expected.getTimeInMillis()), format(actual));
+        } finally {
+            TimeZone.setDefault(old);
+        }
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayDstJan() {
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        initial.set(2014, Calendar.JANUARY, 4, 0, 31, 59); // Sat, January 4, 2014, 00:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Sun, January 5, 2014, 00:00
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        expected.set(2014, Calendar.JANUARY, 5, 0, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayDstJun() {
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-5"));
+        initial.set(2014, Calendar.JUNE, 4, 0, 31, 59); // Wed, March 4, 2014, 00:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Thu, March 5, 2014, 00:00
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-5"));
+        expected.set(2014, Calendar.JUNE, 5, 0, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayDstStart() {
+        // America/Chicago 2014 - DST start - Mar 9 02:00
+        // during winter GMT-6
+        // during summer GMT-5
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-6"));
+        initial.set(2014, Calendar.MARCH, 9, 0, 31, 59); // Sun, March 9, 2014, 00:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Mon, March 10, 2014, 00:00
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-5")); // offset changed
+        expected.set(2014, Calendar.MARCH, 10, 0, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayDstEnd() {
+        // America/Chicago 2014 - DST end - Nov 2 02:00
+        // during summer GMT-5
+        // during winter GMT-6
+        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT-5"));
+        initial.set(2014, Calendar.NOVEMBER, 2, 0, 31, 59); // Sun, November 2, 2014, 00:31
+        final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+        // expect Mon, November 3, 2014, 00:00
+        final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT-6")); // offset changed
+        expected.set(2014, Calendar.NOVEMBER, 3, 0, 0, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        assertEquals(format(expected.getTimeInMillis()), format(actual));
+    }
+
+    @Test
+    @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
+    @ResourceLock(value = Resources.TIME_ZONE)
+    public void testGetNextTimeDailyReturnsFirstHourOfNextDayInGmtIfZoneIsInvalid() {
+        TimeZone old = TimeZone.getDefault();
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT-10")); // default is ignored even if timezone option invalid
+        try {
+            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{NOTVALID}.log.gz");
+            final Calendar initial = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+            initial.set(2014, Calendar.MARCH, 4, 2, 31, 59); // Tue, March 4, 2014, 02:31
+            final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false);
+
+            // expect Wed, March 5, 2014, 00:00
+            final Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+            expected.set(2014, Calendar.MARCH, 5, 0, 0, 0);
+            expected.set(Calendar.MILLISECOND, 0);
+            assertEquals(format(expected.getTimeInMillis()), format(actual));
+        } finally {
+            TimeZone.setDefault(old);
+        }
+    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
index e508ece465..ed73e0fe3c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
@@ -16,6 +16,13 @@
  */
 package org.apache.logging.log4j.core.appender.rolling;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
@@ -28,12 +35,6 @@ import org.apache.logging.log4j.core.pattern.PatternConverter;
 import org.apache.logging.log4j.core.pattern.PatternParser;
 import org.apache.logging.log4j.status.StatusLogger;
 
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-
 /**
  * Parses the rollover pattern.
  */
@@ -62,6 +63,7 @@ public class PatternProcessor {
     private boolean isTimeBased = false;
 
     private RolloverFrequency frequency = null;
+    private TimeZone timeZone;
 
     private final String pattern;
 
@@ -95,12 +97,16 @@ public class PatternProcessor {
             if (converter instanceof DatePatternConverter) {
                 final DatePatternConverter dateConverter = (DatePatternConverter) converter;
                 frequency = calculateFrequency(dateConverter.getPattern());
+                timeZone = dateConverter.getTimeZone();
             } else if (converter instanceof FileDatePatternConverter) {
-                frequency = calculateFrequency(((FileDatePatternConverter) converter).getPattern());
+                final FileDatePatternConverter dateConverter = (FileDatePatternConverter) converter;
+                frequency = calculateFrequency(dateConverter.getPattern());
+                timeZone = dateConverter.getTimeZone();
             }
         }
     }
 
+
     /**
      * Copy constructor with another pattern as source.
      *
@@ -165,9 +171,9 @@ public class PatternProcessor {
         if (frequency == null) {
             throw new IllegalStateException("Pattern '" + pattern + "' does not contain a date");
         }
-        final Calendar currentCal = Calendar.getInstance();
+        final Calendar currentCal = Calendar.getInstance(timeZone);
         currentCal.setTimeInMillis(currentMillis);
-        final Calendar cal = Calendar.getInstance();
+        final Calendar cal = Calendar.getInstance(timeZone);
         currentCal.setMinimalDaysInFirstWeek(7);
         cal.setMinimalDaysInFirstWeek(7);
         cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
@@ -238,9 +244,9 @@ public class PatternProcessor {
 
     public void updateTime() {
         if (nextFileTime != 0 || !isTimeBased) {
-			prevFileTime = nextFileTime;
-			currentFileTime = 0;
-		}
+            prevFileTime = nextFileTime;
+            currentFileTime = 0;
+        }
     }
 
     private long debugGetNextTime(final long nextTime) {
@@ -293,9 +299,9 @@ public class PatternProcessor {
                                      final Object obj) {
         // LOG4J2-628: we deliberately use System time, not the log4j.Clock time
         // for creating the file name of rolled-over files.
-		LOGGER.debug("Formatting file name. useCurrentTime={}, currentFileTime={}, prevFileTime={}, nextFileTime={}",
-			useCurrentTime, currentFileTime, prevFileTime, nextFileTime);
-		final long time = useCurrentTime ? currentFileTime != 0 ? currentFileTime : System.currentTimeMillis() :
+        LOGGER.debug("Formatting file name. useCurrentTime={}, currentFileTime={}, prevFileTime={}, nextFileTime={}",
+            useCurrentTime, currentFileTime, prevFileTime, nextFileTime);
+        final long time = useCurrentTime ? currentFileTime != 0 ? currentFileTime : System.currentTimeMillis() :
                 prevFileTime != 0 ? prevFileTime : System.currentTimeMillis();
         formatFileName(buf, new Date(time), obj);
         final LogEvent event = new Log4jLogEvent.Builder().setTimeMillis(time).build();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index 6612452133..2cd86bdfaf 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -55,6 +55,10 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         public String toPattern() {
             return null;
         }
+
+        public TimeZone getTimeZone() {
+            return TimeZone.getDefault();
+        }
     }
 
     private static final class PatternFormatter extends Formatter {
@@ -86,6 +90,11 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         public String toPattern() {
             return fastDateFormat.getPattern();
         }
+
+        @Override
+        public TimeZone getTimeZone() {
+            return fastDateFormat.getTimeZone();
+        }
     }
 
     private static final class FixedFormatter extends Formatter {
@@ -120,6 +129,11 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         public String toPattern() {
             return fixedDateFormat.getFormat();
         }
+
+        @Override
+        public TimeZone getTimeZone() {
+            return fixedDateFormat.getTimeZone();
+        }
     }
 
     private static final class UnixFormatter extends Formatter {
@@ -356,4 +370,12 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         return formatter.toPattern();
     }
 
+    /**
+     * Gets the timezone used by this date format.
+     *
+     * @return the timezone used by this date format.
+     */
+    public TimeZone getTimeZone() {
+        return formatter.getTimeZone();
+    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
index 0b9cbde9de..9b40a388b7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.core.pattern;
 
+import java.util.TimeZone;
+
 import org.apache.logging.log4j.plugins.Namespace;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -77,4 +79,9 @@ public final class FileDatePatternConverter implements ArrayPatternConverter {
     public String getPattern() {
         return delegate.getPattern();
     }
+
+    public TimeZone getTimeZone() {
+        return delegate.getTimeZone();
+    }
+
 }