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