View Javadoc

1   /*
2    * Copyright (c) 2006-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.support.logging.util;
14  
15  import java.io.BufferedOutputStream;
16  import java.io.File;
17  import java.io.FileOutputStream;
18  import java.io.IOException;
19  import java.io.OutputStream;
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.GregorianCalendar;
23  
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  /**
28   * <p>Simple implementation of {@link LogFileRotator} interface. This implementation
29   * knows how to keep certain number of generations of log files,
30   * to rotate logs when size exceeds or age of file (age is calculated since file
31   * is created by an instance of this class) or at one predefined moment during the day.
32   * </p>
33   * <p>
34   * Each time this class is instantiated log rotation is enforced. That is done
35   * in the lazy manner - first call to {@link #logFile()} method will do so.
36   * </p>
37   * @author Daniel Sendula
38   */
39  public class LogFileRotatorImpl implements LogFileRotator {
40  
41      /** Logger */
42      protected final Logger logger = LoggerFactory.getLogger(getClass());
43  
44      /** Cached output stream */
45      protected OutputStream cachedOutputStream;
46  
47      /** Log file */
48      protected File logFile;
49  
50      /** Log file path */
51      protected File logPath;
52  
53      /** Number of generations. -1 if unlimited or 0 if no generations of the file to be kept */
54      private int numberOfGenerations = -1;
55  
56      /** Maximum size of the file in bytes or -1 if no maximum is defined */
57      private long maxSize = -1;
58  
59      /** Maximum age of the fime in milliseconds or -1 if no maximum age is defined */
60      private long maxAge = -1;
61  
62      /** Time of the day in the form of a calendar. May be null */
63      private Calendar timeOfDay = null;
64  
65      /**
66       * Amount of time before check for log rotation is made. This is performance optimisation.
67       * Default is 100
68       */
69      private int checkDelayMillis = 100;
70  
71      /** When is this object lass accessed through {@link #logFile()} method. */
72      protected long lastAccessed;
73  
74      /** When is scheduled next rotation based on the time of the day or 0 if none */
75      protected long nextRotate;
76  
77      /** When is log file created */
78      protected long logFileCreated;
79  
80      /** Buffer len for output file or -1 if none. */
81      private int bufferLen = -1;
82  
83      /**
84       * Constructor
85       */
86      public LogFileRotatorImpl() {
87      }
88  
89      /**
90       * Constructor
91       *
92       * @param logFile log file handle
93       */
94      public LogFileRotatorImpl(File logFile) {
95          this.setLogFile(logFile);
96      }
97  
98      /**
99       * This method first checks if we have accessed it too quickly since
100      * last time according to the {@link #checkDelayMillis} field. If so
101      * check should log be rotated or not wont be done.
102      *
103      * @returns output stream of the log file
104      * @throws IOException io exception
105      */
106     public synchronized OutputStream logFile() throws IOException {
107         if ((System.currentTimeMillis() - lastAccessed) > checkDelayMillis) {
108             lastAccessed = System.currentTimeMillis();
109             check();
110         }
111         return cachedOutputStream;
112     }
113 
114     /**
115      * Returns file handle of the log file.
116      * @return file handle of the log file
117      */
118     public File getLogFile() {
119         return logFile;
120     }
121 
122     /**
123      * Sets file handle of the log file
124      * @param logFile file handle of the log file
125      */
126     public void setLogFile(File logFile) {
127         this.logFile = logFile;
128         this.logPath = logFile.getParentFile();
129     }
130 
131     /**
132      * Returns the direcotry of log file.
133      * .
134      * @return the direcotry of log file
135      * @see #setLogDirectory(File)
136      */
137     public File getLogDirectory() {
138         return logPath;
139     }
140 
141     /**
142      * Sets log directory. This is only convenience setter to be used
143      * in conjuction with {@link #setLogFileName(String)} instead
144      * of {@link #setLogFile(File)}. There is no
145      * need using these two (this and {@link #setLogFileName(String)} in
146      * log file is set through {@link #setLogFile(File)}.
147      * Settin only log directory will assume log file name
148      * as &quot;access.log&quot. If you want
149      * to set log directory and use different name use {@link #setLogFileName(String)} to
150      * set the name after you have
151      * set the directory using this method
152      *
153      * @param logPath log file directory
154      */
155     public void setLogDirectory(File logPath) {
156         this.logPath = logPath;
157         this.logFile = new File(logPath, "access.log");
158     }
159 
160     /**
161      * Returns last part of log file's path
162      * @return last part of log file's path
163      */
164     public String getLogFileName() {
165         return logFile.getName();
166     }
167 
168     /**
169      * Sets last part of a path of log file. Seet {@link #setLogDirectory(File)} for more explanations.
170      * @param name
171      */
172     public void setLogFileName(String name) {
173         this.logFile = new File(logPath, name);
174     }
175 
176     /**
177      * Returns maximum age of the file.
178      *
179      * @see #maxAge
180      * @return maximum age of the file
181      */
182     public long getMaxAge() {
183         return maxAge;
184     }
185 
186     /**
187      * Sets maximum age of the file
188      *
189      * @see #maxAge
190      * @param maxAge maximum age of the file
191      */
192     public void setMaxAge(long maxAge) {
193         this.maxAge = maxAge;
194     }
195 
196 
197     /**
198      * Returns maximum size of the file.
199      *
200      * @see #maxSize
201      * @return maximum size of the file
202      */
203     public long getMaxSize() {
204         return maxSize;
205     }
206 
207     /**
208      * Sets maximum size of the file
209      *
210      * @see #maxSize
211      * @param maxAge maximum size of the file
212      */
213     public void setMaxSize(long maxSize) {
214         this.maxSize = maxSize;
215     }
216 
217     /**
218      * Returns defined number of generations
219      *
220      * @see #numberOfGenerations
221      * @return defined number of generations
222      */
223     public int getNumberOfGenerations() {
224         return numberOfGenerations;
225     }
226 
227     /**
228      * Sets defined number of generations
229      *
230      * @see #numberOfGenerations
231      * @param numberOfGeneration defined number of generations
232      */
233     public void setNumberOfGenerations(int numberOfGeneration) {
234         this.numberOfGenerations = numberOfGeneration;
235     }
236 
237     /**
238      * Returns time of day when rotation is going to happen.
239      *
240      * @see #timeOfDay
241      * @return time of day when rotation is going to happen
242      */
243     public Calendar getTimeOfDay() {
244         return timeOfDay;
245     }
246 
247     /**
248      * Sets time of day when rotation is going to happen
249      *
250      * @see #timeOfDay
251      * @param timeOfDay time of day when rotation is going to happen
252      */
253     public void setTimeOfDay(Calendar timeOfDay) {
254         this.timeOfDay = timeOfDay;
255         if (timeOfDay == null) {
256             nextRotate = 0;
257         } else {
258             GregorianCalendar cal = new GregorianCalendar();
259             cal.set(Calendar.HOUR, timeOfDay.get(Calendar.HOUR));
260             cal.set(Calendar.MINUTE, timeOfDay.get(Calendar.MINUTE));
261             cal.set(Calendar.SECOND, timeOfDay.get(Calendar.SECOND));
262             cal.set(Calendar.MILLISECOND, timeOfDay.get(Calendar.MILLISECOND));
263             cal.add(Calendar.DAY_OF_YEAR, 1);
264             nextRotate = cal.getTimeInMillis();
265         }
266     }
267 
268     /**
269      * Returns check delay in milliseconds
270      *
271      * @see #checkDelayMillis
272      * @return check delay in milliseconds
273      */
274     public int getCheckDelayMillis() {
275         return checkDelayMillis;
276     }
277 
278     /**
279      * Sets check delay in milliseconds
280      *
281      * @see #checkDelayMillis
282      * @param checkDelayMillis check delay in milliseconds
283      */
284     public void setCheckDelayMillis(int checkDelayMillis) {
285         this.checkDelayMillis = checkDelayMillis;
286     }
287 
288     /**
289      * Returns file buffer len
290      *
291      * @see #bufferLen
292      * @return file buffer len
293      */
294     public int getBufferLen() {
295         return bufferLen;
296     }
297 
298     /**
299      * Sets file buffer len
300      *
301      * @see #bufferLen
302      * @param bufferLen file buffer len
303      */
304     public void setBufferLen(int bufferLen) {
305         this.bufferLen = bufferLen;
306     }
307 
308     /**
309      * Checks if file needs to be rotated
310      *
311      * @throws IOException io exception
312      */
313     protected void check() throws IOException {
314         boolean doRotate = false;
315         if (cachedOutputStream == null) {
316             doRotate = true;
317         }
318         if (!doRotate) {
319             long maxAge = getMaxAge();
320             if ((maxAge > 0) && ((lastAccessed - logFileCreated) > maxAge)) {
321                 doRotate = true;
322             }
323         }
324         if (!doRotate) {
325             long maxSize = getMaxSize();
326             if ((maxSize > 0) && (getLogFile().length()) > maxSize) {
327                 doRotate = true;
328             }
329         }
330         if (!doRotate) {
331             if ((nextRotate > 0) && (lastAccessed > nextRotate)) {
332                 doRotate = true;
333                 GregorianCalendar cal = new GregorianCalendar();
334                 cal.setTimeInMillis(nextRotate);
335                 cal.add(Calendar.DAY_OF_YEAR, 1);
336                 nextRotate = cal.getTimeInMillis();
337             }
338         }
339         if (doRotate) {
340             rotate();
341         }
342     }
343 
344     /**
345      * Rotates the log files
346      *
347      * @throws IOException io exception
348      */
349     public void rotate() throws IOException {
350         if (cachedOutputStream != null) {
351             try {
352                 cachedOutputStream.close();
353             } catch (IOException e) {
354                 logger.error("Failed to close previous file", e);
355             }
356         }
357         ArrayList<File> files = new ArrayList<File>();
358         int i = 1;
359         File f = createFileName(i);
360         while (f.exists()) {
361             if ((numberOfGenerations >= 0) && (i >= numberOfGenerations)) {
362                 f.delete();
363             }
364             if ((numberOfGenerations < 0) || (i <= numberOfGenerations)) {
365                 files.add(f);
366             }
367             i++;
368             f = createFileName(i);
369         }
370         if ((numberOfGenerations < 0) || (i <= numberOfGenerations)) {
371             files.add(createFileName(i));
372             i++;
373         }
374         i = i - 1;
375         if (i > 1) {
376             for (int j = i-1; j >= 1; j--) {
377                 File last = files.get(j);
378                 File second = files.get(j-1);
379                 second.renameTo(last);
380             }
381         }
382         if (numberOfGenerations != 0) {
383             getLogFile().renameTo(createFileName(1));
384         } else {
385             getLogFile().delete();
386         }
387         createLogFile();
388     }
389 
390     /**
391      * Creates a file name from given generation number.
392      * If 0 is supplied for generation number, then {@link #logFile} i sreturned
393      *
394      * @param g generation number
395      * @return a file name from given generation number
396      */
397     protected File createFileName(int g) {
398         if (g == 0) {
399             return getLogFile();
400         } else {
401             File p = getLogFile().getParentFile();
402             if (p == null) {
403                 p = new File(".");
404             }
405             String name = getLogFile().getName();
406             int i = name.lastIndexOf('.');
407             if (i >= 0) {
408                 name = name.substring(0, i) + "." + g + name.substring(i);
409             } else {
410                 name = name + "." + g;
411             }
412             return new File(p, name);
413         }
414     }
415 
416     /**
417      * Creates log file
418      *
419      * @throws IOException io exception
420      */
421     protected void createLogFile() throws IOException {
422         File file = getLogFile();
423         FileOutputStream fos = new FileOutputStream(file);
424         if (getBufferLen() > 0) {
425             BufferedOutputStream out = new BufferedOutputStream(fos, getBufferLen());
426             cachedOutputStream = out;
427         } else {
428             cachedOutputStream = fos;
429         }
430         logFileCreated = file.lastModified();
431     }
432 }