1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.rolling;
15  
16  import ch.qos.logback.classic.ClassicTestConstants;
17  import ch.qos.logback.classic.Logger;
18  import ch.qos.logback.classic.LoggerContext;
19  import ch.qos.logback.classic.joran.JoranConfigurator;
20  import ch.qos.logback.classic.spi.ILoggingEvent;
21  import ch.qos.logback.classic.util.LogbackMDCAdapter;
22  import ch.qos.logback.core.CoreConstants;
23  import ch.qos.logback.core.joran.spi.JoranException;
24  import ch.qos.logback.core.rolling.RollingFileAppender;
25  import ch.qos.logback.core.rolling.TimeBasedFileNamingAndTriggeringPolicy;
26  import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
27  import ch.qos.logback.core.rolling.testUtil.ParentScaffoldingForRollingTests;
28  import ch.qos.logback.core.rolling.testUtil.ScaffoldingForRollingTests;
29  import ch.qos.logback.core.status.Status;
30  import ch.qos.logback.core.status.testUtil.StatusChecker;
31  import ch.qos.logback.core.util.StatusPrinter;
32  import org.junit.jupiter.api.AfterEach;
33  import org.junit.jupiter.api.BeforeEach;
34  import org.junit.jupiter.api.Test;
35  
36  import java.util.Date;
37  
38  import static org.junit.jupiter.api.Assertions.assertFalse;
39  import static org.junit.jupiter.api.Assertions.assertTrue;
40  
41  public class TimeBasedRollingWithConfigFileTest extends ScaffoldingForRollingTests {
42  
43      LoggerContext loggerContext = new LoggerContext();
44      LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
45      StatusChecker statusChecker = new StatusChecker(loggerContext);
46      Logger logger = loggerContext.getLogger(this.getClass());
47      int fileSize = 0;
48      int fileIndexCounter = -1;
49      int sizeThreshold;
50  
51      @BeforeEach
52      @Override
53      public void setUp() {
54          loggerContext.setName("test");
55          loggerContext.setMDCAdapter(logbackMDCAdapter);
56          super.setUp();
57          loggerContext.putProperty("randomOutputDir", randomOutputDir);
58      }
59  
60      @AfterEach
61      public void tearDown() throws Exception {
62      }
63  
64      void loadConfig(String confifFile) throws JoranException {
65          JoranConfigurator jc = new JoranConfigurator();
66          jc.setContext(loggerContext);
67          jc.doConfigure(confifFile);
68          currentTime = System.currentTimeMillis();
69          recomputeRolloverThreshold(currentTime);
70      }
71  
72      @Test
73      public void basic() throws Exception {
74          String testId = "basic";
75          loggerContext.putProperty("testId", testId);
76          loadConfig(ClassicTestConstants.JORAN_INPUT_PREFIX + "rolling/" + testId + ".xml");
77          statusChecker.assertIsErrorFree();
78  
79          Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
80  
81          expectedFilenameList.add(randomOutputDir + "z" + testId);
82  
83          RollingFileAppender<ILoggingEvent> rfa = (RollingFileAppender<ILoggingEvent>) root.getAppender("ROLLING");
84  
85          TimeBasedRollingPolicy<ILoggingEvent> tprp = (TimeBasedRollingPolicy<ILoggingEvent>) rfa.getTriggeringPolicy();
86          TimeBasedFileNamingAndTriggeringPolicy<ILoggingEvent> tbnatp = tprp.getTimeBasedFileNamingAndTriggeringPolicy();
87  
88          String prefix = "Hello---";
89          int runLength = 4;
90          for (int i = 0; i < runLength; i++) {
91              logger.debug(prefix + i);
92              addExpectedFileNamedIfItsTime_ByDate(randomOutputDir, testId, false);
93              incCurrentTime(500);
94              tbnatp.setCurrentTime(currentTime);
95          }
96  
97          existenceCheck(expectedFilenameList);
98          sortedContentCheck(randomOutputDir, runLength, prefix);
99      }
100 
101     @Test
102     public void depratedSizeAndTimeBasedFNATPWarning() throws Exception {
103         String testId = "depratedSizeAndTimeBasedFNATPWarning";
104         loggerContext.putProperty("testId", testId);
105         loadConfig(ClassicTestConstants.JORAN_INPUT_PREFIX + "rolling/" + testId + ".xml");
106         StatusPrinter.print(loggerContext);
107         statusChecker.assertContainsMatch(Status.WARN, CoreConstants.SIZE_AND_TIME_BASED_FNATP_IS_DEPRECATED);
108     }
109 
110     @Test
111     public void timeAndSize() throws Exception {
112         String testId = "timeAndSize";
113         loggerContext.putProperty("testId", testId);
114         String prefix = "Hello-----";
115 
116         // the number of times the log file will be written to before time based
117         // roll-over occurs
118         int approxWritesPerPeriod = 64;
119         sizeThreshold = prefix.length() * approxWritesPerPeriod;
120         loggerContext.putProperty("sizeThreshold", "" + sizeThreshold);
121         System.out.println("timeAndSize.sizeThreshold="+sizeThreshold);
122         loadConfig(ClassicTestConstants.JORAN_INPUT_PREFIX + "rolling/" + testId + ".xml");
123 
124         StatusPrinter.print(loggerContext);
125         // Test http://jira.qos.ch/browse/LOGBACK-1236
126         statusChecker.assertNoMatch(CoreConstants.SIZE_AND_TIME_BASED_FNATP_IS_DEPRECATED);
127 
128         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
129 
130         expectedFilenameList.add(randomOutputDir + "z" + testId);
131 
132         RollingFileAppender<ILoggingEvent> rfa = (RollingFileAppender<ILoggingEvent>) root.getAppender("ROLLING");
133 
134         statusChecker.assertIsErrorFree();
135 
136         TimeBasedRollingPolicy<ILoggingEvent> tprp = (TimeBasedRollingPolicy<ILoggingEvent>) rfa.getTriggeringPolicy();
137         TimeBasedFileNamingAndTriggeringPolicy<ILoggingEvent> tbnatp = tprp.getTimeBasedFileNamingAndTriggeringPolicy();
138 
139         int timeIncrement = 1000 / approxWritesPerPeriod;
140         int targetPeriodCount = 3;
141         int runLength = approxWritesPerPeriod * targetPeriodCount;
142         for (int i = 0; i < runLength; i++) {
143             String msg = prefix + i;
144             logger.debug(msg);
145             addExpectedFileNamedIfItsTime(testId, msg, false);
146             incCurrentTime(timeIncrement);
147             tbnatp.setCurrentTime(currentTime);
148         }
149 
150         sortedContentCheck(randomOutputDir, runLength, prefix);
151         int eCount = existenceCount(expectedFilenameList);
152         // for various reasons, it is extremely difficult to have the files
153         // match exactly the expected archive files. Thus, we aim for
154         // an approximate match
155         assertTrue(eCount >= targetPeriodCount || eCount >= expectedFilenameList.size() / 2,
156                 "existenceCount=" + eCount + ", expectedFilenameList.size=" + expectedFilenameList.size());
157     }
158 
159     @Test
160     public void timeAndSizeWithoutIntegerToken() throws Exception {
161         String testId = "timeAndSizeWithoutIntegerToken";
162         loadConfig(ClassicTestConstants.JORAN_INPUT_PREFIX + "rolling/" + testId + ".xml");
163         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
164         expectedFilenameList.add(randomOutputDir + "z" + testId);
165         RollingFileAppender<ILoggingEvent> rfa = (RollingFileAppender<ILoggingEvent>) root.getAppender("ROLLING");
166         StatusPrinter.print(loggerContext);
167 
168         statusChecker.assertContainsMatch("Missing integer token");
169         assertFalse(rfa.isStarted());
170     }
171 
172     // see also LOGBACK-1176
173     @Test
174     public void timeAndSizeWithoutMaxFileSize() throws Exception {
175         String testId = "timeAndSizeWithoutMaxFileSize";
176         loadConfig(ClassicTestConstants.JORAN_INPUT_PREFIX + "rolling/" + testId + ".xml");
177         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
178         // expectedFilenameList.add(randomOutputDir + "z" + testId);
179         RollingFileAppender<ILoggingEvent> rfa = (RollingFileAppender<ILoggingEvent>) root.getAppender("ROLLING");
180 
181         // statusChecker.assertContainsMatch("Missing integer token");
182         assertFalse(rfa.isStarted());
183         StatusPrinter.print(loggerContext);
184     }
185 
186     @Test
187     public void totalSizeCapSmallerThanMaxFileSize() throws Exception {
188         String testId = "totalSizeCapSmallerThanMaxFileSize";
189         loggerContext.putProperty("testId", testId);
190         loadConfig(ClassicTestConstants.JORAN_INPUT_PREFIX + "rolling/" + testId + ".xml");
191         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
192         // expectedFilenameList.add(randomOutputDir + "z" + testId);
193         RollingFileAppender<ILoggingEvent> rfa = (RollingFileAppender<ILoggingEvent>) root.getAppender("ROLLING");
194 
195         // totalSizeCap of [10 Bytes] is much smaller than maxFileSize [250 Bytes] which is non-sensical, even taking compression into account.
196         statusChecker.assertContainsMatch(Status.WARN,
197                 "totalSizeCap of \\[\\d* \\w*\\] is much smaller than maxFileSize \\[\\d* \\w*\\] which is non-sensical, even taking compression into account.");
198         assertTrue(rfa.isStarted());
199 
200     }
201 
202     void addExpectedFileNamedIfItsTime(String testId, String msg, boolean gzExtension) {
203         fileSize += msg.getBytes().length;
204 
205         if (passThresholdTime(nextRolloverThreshold)) {
206             fileIndexCounter = 0;
207             fileSize = 0;
208             addExpectedFileName(testId, getDateOfPreviousPeriodsStart(), fileIndexCounter, gzExtension);
209             recomputeRolloverThreshold(currentTime);
210             return;
211         }
212 
213         // windows can delay file size changes, so we only allow for
214         // fileIndexCounter 0 and 1
215         if ((fileIndexCounter < 1) && fileSize > sizeThreshold) {
216             addExpectedFileName(testId, getDateOfPreviousPeriodsStart(), ++fileIndexCounter, gzExtension);
217             fileSize = -1;
218             return;
219         }
220     }
221 
222     void addExpectedFileName(String testId, Date date, int fileIndexCounter, boolean gzExtension) {
223 
224         String fn = randomOutputDir + testId + "-" + SDF.format(date) + "." + fileIndexCounter;
225         if (gzExtension) {
226             fn += ".gz";
227         }
228         System.out.println("Adding " + fn);
229         expectedFilenameList.add(fn);
230     }
231 
232     @Override
233     protected void addExpectedFileNamedIfItsTime_ByDate(String outputDir, String testId, boolean gzExtension) {
234         if (passThresholdTime(nextRolloverThreshold)) {
235             addExpectedFileName_ByDate(outputDir, testId, getDateOfPreviousPeriodsStart(), gzExtension);
236             recomputeRolloverThreshold(currentTime);
237         }
238     }
239 }