View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender.rolling;
18  
19  import org.apache.logging.log4j.Logger;
20  import org.apache.logging.log4j.core.appender.rolling.helper.Action;
21  import org.apache.logging.log4j.core.appender.rolling.helper.FileRenameAction;
22  import org.apache.logging.log4j.core.appender.rolling.helper.GZCompressAction;
23  import org.apache.logging.log4j.core.appender.rolling.helper.ZipCompressAction;
24  import org.apache.logging.log4j.core.config.Configuration;
25  import org.apache.logging.log4j.core.config.plugins.Plugin;
26  import org.apache.logging.log4j.core.config.plugins.PluginAttr;
27  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
28  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
29  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
30  import org.apache.logging.log4j.status.StatusLogger;
31  
32  import java.io.File;
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  /**
37   * When rolling over, <code>FixedWindowRollingPolicy</code> renames files
38   * according to a fixed window algorithm as described below.
39   * <p/>
40   * <p>The <b>ActiveFileName</b> property, which is required, represents the name
41   * of the file where current logging output will be written.
42   * The <b>FileNamePattern</b>  option represents the file name pattern for the
43   * archived (rolled over) log files. If present, the <b>FileNamePattern</b>
44   * option must include an integer token, that is the string "%i" somewhere
45   * within the pattern.
46   * <p/>
47   * <p>Let <em>max</em> and <em>min</em> represent the values of respectively
48   * the <b>MaxIndex</b> and <b>MinIndex</b> options. Let "foo.log" be the value
49   * of the <b>ActiveFile</b> option and "foo.%i.log" the value of
50   * <b>FileNamePattern</b>. Then, when rolling over, the file
51   * <code>foo.<em>max</em>.log</code> will be deleted, the file
52   * <code>foo.<em>max-1</em>.log</code> will be renamed as
53   * <code>foo.<em>max</em>.log</code>, the file <code>foo.<em>max-2</em>.log</code>
54   * renamed as <code>foo.<em>max-1</em>.log</code>, and so on,
55   * the file <code>foo.<em>min+1</em>.log</code> renamed as
56   * <code>foo.<em>min+2</em>.log</code>. Lastly, the active file <code>foo.log</code>
57   * will be renamed as <code>foo.<em>min</em>.log</code> and a new active file name
58   * <code>foo.log</code> will be created.
59   * <p/>
60   * <p>Given that this rollover algorithm requires as many file renaming
61   * operations as the window size, large window sizes are discouraged. The
62   * current implementation will automatically reduce the window size to 12 when
63   * larger values are specified by the user.
64   */
65  @Plugin(name = "DefaultRolloverStrategy", type = "Core", printObject = true)
66  public class DefaultRolloverStrategy implements RolloverStrategy {
67      /**
68       * Allow subclasses access to the status logger without creating another instance.
69       */
70      protected static final Logger LOGGER = StatusLogger.getLogger();
71  
72      private static final int MIN_WINDOW_SIZE = 1;
73      private static final int DEFAULT_WINDOW_SIZE = 7;
74  
75      /**
76       * Index for oldest retained log file.
77       */
78      private final int maxIndex;
79  
80      /**
81       * Index for most recent log file.
82       */
83      private final int minIndex;
84  
85      private final StrSubstitutor subst;
86  
87      /**
88       * Constructs a new instance.
89       * @param min The minimum index.
90       * @param max The maximum index.
91       */
92      protected DefaultRolloverStrategy(int min, int max, StrSubstitutor subst) {
93          minIndex = min;
94          maxIndex = max;
95          this.subst = subst;
96      }
97  
98      /**
99       * Perform the rollover.
100      * @param manager The RollingFileManager name for current active log file.
101      * @return A RolloverDescription.
102      * @throws SecurityException if an error occurs.
103      */
104     public RolloverDescription rollover(RollingFileManager manager) throws SecurityException {
105         if (maxIndex >= 0) {
106             int purgeStart = minIndex;
107 
108             if (!purge(purgeStart, maxIndex, manager)) {
109                 return null;
110             }
111 
112             StringBuilder buf = new StringBuilder();
113             manager.getProcessor().formatFileName(buf, purgeStart);
114             String currentFileName = manager.getFileName();
115 
116             String renameTo = subst.replace(buf);
117             String compressedName = renameTo;
118             Action compressAction = null;
119 
120             if (renameTo.endsWith(".gz")) {
121                 renameTo = renameTo.substring(0, renameTo.length() - 3);
122                 compressAction = new GZCompressAction(new File(renameTo), new File(compressedName), true);
123             } else if (renameTo.endsWith(".zip")) {
124                 renameTo = renameTo.substring(0, renameTo.length() - 4);
125                 compressAction = new ZipCompressAction(new File(renameTo), new File(compressedName), true);
126             }
127 
128             FileRenameAction renameAction =
129                 new FileRenameAction(new File(currentFileName), new File(renameTo), false);
130 
131             return new RolloverDescriptionImpl(currentFileName, false, renameAction, compressAction);
132         }
133 
134         return null;
135     }
136 
137     /**
138      * Purge and rename old log files in preparation for rollover
139      *
140      * @param lowIndex  low index
141      * @param highIndex high index.  Log file associated with high index will be deleted if needed.
142      * @param manager The RollingFileManager
143      * @return true if purge was successful and rollover should be attempted.
144      */
145     private boolean purge(final int lowIndex, final int highIndex, RollingFileManager manager) {
146         int suffixLength = 0;
147 
148         List<FileRenameAction> renames = new ArrayList<FileRenameAction>();
149         StringBuilder buf = new StringBuilder();
150         manager.getProcessor().formatFileName(buf, lowIndex);
151 
152         String lowFilename = subst.replace(buf);
153 
154         if (lowFilename.endsWith(".gz")) {
155             suffixLength = 3;
156         } else if (lowFilename.endsWith(".zip")) {
157             suffixLength = 4;
158         }
159 
160         for (int i = lowIndex; i <= highIndex; i++) {
161             File toRename = new File(lowFilename);
162             boolean isBase = false;
163 
164             if (suffixLength > 0) {
165                 File toRenameBase =
166                     new File(lowFilename.substring(0, lowFilename.length() - suffixLength));
167 
168                 if (toRename.exists()) {
169                     if (toRenameBase.exists()) {
170                         toRenameBase.delete();
171                     }
172                 } else {
173                     toRename = toRenameBase;
174                     isBase = true;
175                 }
176             }
177 
178             if (toRename.exists()) {
179                 //
180                 //    if at upper index then
181                 //        attempt to delete last file
182                 //        if that fails then abandon purge
183                 if (i == highIndex) {
184                     if (!toRename.delete()) {
185                         return false;
186                     }
187 
188                     break;
189                 }
190 
191                 //
192                 //   if intermediate index
193                 //     add a rename action to the list
194                 buf.setLength(0);
195                 manager.getProcessor().formatFileName(buf, i + 1);
196 
197                 String highFilename = subst.replace(buf);
198                 String renameTo = highFilename;
199 
200                 if (isBase) {
201                     renameTo = highFilename.substring(0, highFilename.length() - suffixLength);
202                 }
203 
204                 renames.add(new FileRenameAction(toRename, new File(renameTo), true));
205                 lowFilename = highFilename;
206             } else {
207                 break;
208             }
209         }
210 
211         //
212         //   work renames backwards
213         //
214         for (int i = renames.size() - 1; i >= 0; i--) {
215             Action action = renames.get(i);
216 
217             try {
218                 if (!action.execute()) {
219                     return false;
220                 }
221             } catch (Exception ex) {
222                 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
223                 return false;
224             }
225         }
226 
227         return true;
228     }
229 
230     @Override
231     public String toString() {
232         return "DefaultRolloverStrategy(min=" + minIndex + ", max=" + maxIndex + ")";
233     }
234 
235     /**
236      * Create the DefaultRolloverStrategy.
237      * @param max The maximum number of files to keep.
238      * @param min The minimum number of files to keep.
239      * @param config The Configuration.
240      * @return A DefaultRolloverStrategy.
241      */
242     @PluginFactory
243     public static DefaultRolloverStrategy createStrategy(@PluginAttr("max") String max,
244                                                          @PluginAttr("min") String min,
245                                                          @PluginConfiguration Configuration config) {
246 
247         int minIndex;
248         if (min != null) {
249             minIndex = Integer.parseInt(min);
250             if (minIndex < 1) {
251                 LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
252                 minIndex = MIN_WINDOW_SIZE;
253             }
254         } else {
255             minIndex = MIN_WINDOW_SIZE;
256         }
257         int maxIndex;
258         if (max != null) {
259             maxIndex = Integer.parseInt(max);
260             if (maxIndex < minIndex) {
261                 maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
262                 LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
263             }
264         } else {
265             maxIndex = DEFAULT_WINDOW_SIZE;
266         }
267         return new DefaultRolloverStrategy(minIndex, maxIndex, config.getSubst());
268     }
269 
270 }