View Javadoc

1   //========================================================================
2   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
3   //------------------------------------------------------------------------
4   //Licensed under the Apache License, Version 2.0 (the "License");
5   //you may not use this file except in compliance with the License.
6   //You may obtain a copy of the License at
7   //http://www.apache.org/licenses/LICENSE-2.0
8   //Unless required by applicable law or agreed to in writing, software
9   //distributed under the License is distributed on an "AS IS" BASIS,
10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  //See the License for the specific language governing permissions and
12  //limitations under the License.
13  //========================================================================
14  
15  package org.mortbay.util; 
16  
17  import java.io.File;
18  import java.io.FileOutputStream;
19  import java.io.FilterOutputStream;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.text.SimpleDateFormat;
23  import java.util.Calendar;
24  import java.util.Date;
25  import java.util.GregorianCalendar;
26  import java.util.TimeZone;
27  import java.util.Timer;
28  import java.util.TimerTask;
29  
30  /** 
31   * RolloverFileOutputStream
32   * 
33   * This output stream puts content in a file that is rolled over every 24 hours.
34   * The filename must include the string "yyyy_mm_dd", which is replaced with the 
35   * actual date when creating and rolling over the file.
36   * 
37   * Old files are retained for a number of days before being deleted.
38   * 
39   * @author Greg Wilkins 
40   */
41  public class RolloverFileOutputStream extends FilterOutputStream
42  {
43      private static Timer __rollover;
44      
45      final static String YYYY_MM_DD="yyyy_mm_dd";
46  
47      private RollTask _rollTask;
48      private SimpleDateFormat _fileBackupFormat;
49      private SimpleDateFormat _fileDateFormat;
50  
51      private String _filename;
52      private File _file;
53      private boolean _append;
54      private int _retainDays;
55      
56      /* ------------------------------------------------------------ */
57      /**
58       * @param filename The filename must include the string "yyyy_mm_dd", 
59       * which is replaced with the actual date when creating and rolling over the file.
60       * @throws IOException
61       */
62      public RolloverFileOutputStream(String filename)
63          throws IOException
64      {
65          this(filename,true,Integer.getInteger("ROLLOVERFILE_RETAIN_DAYS",31).intValue());
66      }
67      
68      /* ------------------------------------------------------------ */
69      /**
70       * @param filename The filename must include the string "yyyy_mm_dd", 
71       * which is replaced with the actual date when creating and rolling over the file.
72       * @param append If true, existing files will be appended to.
73       * @throws IOException
74       */
75      public RolloverFileOutputStream(String filename, boolean append)
76          throws IOException
77      {
78          this(filename,append,Integer.getInteger("ROLLOVERFILE_RETAIN_DAYS",31).intValue());
79      }
80  
81      /* ------------------------------------------------------------ */
82      /**
83       * @param filename The filename must include the string "yyyy_mm_dd", 
84       * which is replaced with the actual date when creating and rolling over the file.
85       * @param append If true, existing files will be appended to.
86       * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
87       * @throws IOException
88       */
89      public RolloverFileOutputStream(String filename,
90                                      boolean append,
91                                      int retainDays)
92          throws IOException
93      {
94          this(filename,append,retainDays,TimeZone.getDefault());
95      }
96  
97      /* ------------------------------------------------------------ */
98      /**
99       * @param filename The filename must include the string "yyyy_mm_dd", 
100      * which is replaced with the actual date when creating and rolling over the file.
101      * @param append If true, existing files will be appended to.
102      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
103      * @throws IOException
104      */
105     public RolloverFileOutputStream(String filename,
106                                     boolean append,
107                                     int retainDays,
108                                     TimeZone zone)
109         throws IOException
110     {
111 
112          this(filename,append,retainDays,zone,null,null);
113     }
114      
115     /* ------------------------------------------------------------ */
116     /**
117      * @param filename The filename must include the string "yyyy_mm_dd", 
118      * which is replaced with the actual date when creating and rolling over the file.
119      * @param append If true, existing files will be appended to.
120      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
121      * @param dateFormat The format for the date file substitution. If null the system property ROLLOVERFILE_DATE_FORMAT is 
122      * used and if that is null, then default is "yyyy_MM_dd". 
123      * @param backupFormat The format for the file extension of backup files. If null the system property
124      * ROLLOVERFILE_BACKUP_FORMAT is used and if that is null, then default is "HHmmssSSS". 
125      * @throws IOException
126      */
127     public RolloverFileOutputStream(String filename,
128                                     boolean append,
129                                     int retainDays,
130                                     TimeZone zone,
131                                     String dateFormat,
132                                     String backupFormat)
133         throws IOException
134     {
135         super(null);
136 
137         if (dateFormat==null)
138             dateFormat=System.getProperty("ROLLOVERFILE_DATE_FORMAT","yyyy_MM_dd");
139         _fileDateFormat = new SimpleDateFormat(dateFormat);
140         
141         if (backupFormat==null)
142             backupFormat=System.getProperty("ROLLOVERFILE_BACKUP_FORMAT","HHmmssSSS");
143         _fileBackupFormat = new SimpleDateFormat(backupFormat);
144         
145         _fileBackupFormat.setTimeZone(zone);
146         _fileDateFormat.setTimeZone(zone);
147         
148         if (filename!=null)
149         {
150             filename=filename.trim();
151             if (filename.length()==0)
152                 filename=null;
153         }
154         if (filename==null)
155             throw new IllegalArgumentException("Invalid filename");
156 
157         _filename=filename;
158         _append=append;
159         _retainDays=retainDays;
160         setFile();
161         
162         synchronized(RolloverFileOutputStream.class)
163         {
164             if (__rollover==null)
165                 __rollover=new Timer(true);
166             
167             _rollTask=new RollTask();
168 
169              Calendar now = Calendar.getInstance();
170              now.setTimeZone(zone);
171 
172              GregorianCalendar midnight =
173                  new GregorianCalendar(now.get(Calendar.YEAR),
174                          now.get(Calendar.MONTH),
175                          now.get(Calendar.DAY_OF_MONTH),
176                          23,0);
177              midnight.setTimeZone(zone);
178              midnight.add(Calendar.HOUR,1);
179              __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
180         }
181     }
182 
183     /* ------------------------------------------------------------ */
184     public String getFilename()
185     {
186         return _filename;
187     }
188     
189     /* ------------------------------------------------------------ */
190     public String getDatedFilename()
191     {
192         if (_file==null)
193             return null;
194         return _file.toString();
195     }
196     
197     /* ------------------------------------------------------------ */
198     public int getRetainDays()
199     {
200         return _retainDays;
201     }
202 
203     /* ------------------------------------------------------------ */
204     private synchronized void setFile()
205         throws IOException
206     {
207         // Check directory
208         File file = new File(_filename);
209         _filename=file.getCanonicalPath();
210         file=new File(_filename);
211         File dir= new File(file.getParent());
212         if (!dir.isDirectory() || !dir.canWrite())
213             throw new IOException("Cannot write log directory "+dir);
214             
215         Date now=new Date();
216         
217         // Is this a rollover file?
218         String filename=file.getName();
219         int i=filename.toLowerCase().indexOf(YYYY_MM_DD);
220         if (i>=0)
221         {
222             file=new File(dir,
223                           filename.substring(0,i)+
224                           _fileDateFormat.format(now)+
225                           filename.substring(i+YYYY_MM_DD.length()));
226         }
227             
228         if (file.exists()&&!file.canWrite())
229             throw new IOException("Cannot write log file "+file);
230 
231         // Do we need to change the output stream?
232         if (out==null || !file.equals(_file))
233         {
234             // Yep
235             _file=file;
236             if (!_append && file.exists())
237                 file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
238             OutputStream oldOut=out;
239             out=new FileOutputStream(file.toString(),_append);
240             if (oldOut!=null)
241                 oldOut.close();
242             //if(log.isDebugEnabled())log.debug("Opened "+_file);
243         }
244     }
245 
246     /* ------------------------------------------------------------ */
247     private void removeOldFiles()
248     {
249         if (_retainDays>0)
250         {
251             long now = System.currentTimeMillis();
252             
253             File file= new File(_filename);
254             File dir = new File(file.getParent());
255             String fn=file.getName();
256             int s=fn.toLowerCase().indexOf(YYYY_MM_DD);
257             if (s<0)
258                 return;
259             String prefix=fn.substring(0,s);
260             String suffix=fn.substring(s+YYYY_MM_DD.length());
261 
262             String[] logList=dir.list();
263             for (int i=0;i<logList.length;i++)
264             {
265                 fn = logList[i];
266                 if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
267                 {        
268                     File f = new File(dir,fn);
269                     long date = f.lastModified();
270                     if ( ((now-date)/(1000*60*60*24))>_retainDays)
271                         f.delete();   
272                 }
273             }
274         }
275     }
276 
277     /* ------------------------------------------------------------ */
278     public void write (byte[] buf)
279             throws IOException
280      {
281             out.write (buf);
282      }
283 
284     /* ------------------------------------------------------------ */
285     public void write (byte[] buf, int off, int len)
286             throws IOException
287      {
288             out.write (buf, off, len);
289      }
290     
291     /* ------------------------------------------------------------ */
292     /** 
293      */
294     public void close()
295         throws IOException
296     {
297         synchronized(RolloverFileOutputStream.class)
298         {
299             try{super.close();}
300             finally
301             {
302                 out=null;
303                 _file=null;
304             }
305 
306             _rollTask.cancel(); 
307         }
308     }
309     
310     /* ------------------------------------------------------------ */
311     /* ------------------------------------------------------------ */
312     /* ------------------------------------------------------------ */
313     private class RollTask extends TimerTask
314     {
315         public void run()
316         {
317             try
318             {
319                 RolloverFileOutputStream.this.setFile();
320                 RolloverFileOutputStream.this.removeOldFiles();
321 
322             }
323             catch(IOException e)
324             {
325                 e.printStackTrace();
326             }
327         }
328     }
329 }