View Javadoc

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