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      final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
46      final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
47      final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
48  
49      private RollTask _rollTask;
50      private SimpleDateFormat _fileBackupFormat;
51      private SimpleDateFormat _fileDateFormat;
52  
53      private String _filename;
54      private File _file;
55      private boolean _append;
56      private int _retainDays;
57      
58      /* ------------------------------------------------------------ */
59      /**
60       * @param filename The filename must include the string "yyyy_mm_dd", 
61       * which is replaced with the actual date when creating and rolling over the file.
62       * @throws IOException
63       */
64      public RolloverFileOutputStream(String filename)
65          throws IOException
66      {
67          this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
68      }
69      
70      /* ------------------------------------------------------------ */
71      /**
72       * @param filename The filename must include the string "yyyy_mm_dd", 
73       * which is replaced with the actual date when creating and rolling over the file.
74       * @param append If true, existing files will be appended to.
75       * @throws IOException
76       */
77      public RolloverFileOutputStream(String filename, boolean append)
78          throws IOException
79      {
80          this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
81      }
82  
83      /* ------------------------------------------------------------ */
84      /**
85       * @param filename The filename must include the string "yyyy_mm_dd", 
86       * which is replaced with the actual date when creating and rolling over the file.
87       * @param append If true, existing files will be appended to.
88       * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
89       * @throws IOException
90       */
91      public RolloverFileOutputStream(String filename,
92                                      boolean append,
93                                      int retainDays)
94          throws IOException
95      {
96          this(filename,append,retainDays,TimeZone.getDefault());
97      }
98  
99      /* ------------------------------------------------------------ */
100     /**
101      * @param filename The filename must include the string "yyyy_mm_dd", 
102      * which is replaced with the actual date when creating and rolling over the file.
103      * @param append If true, existing files will be appended to.
104      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
105      * @throws IOException
106      */
107     public RolloverFileOutputStream(String filename,
108                                     boolean append,
109                                     int retainDays,
110                                     TimeZone zone)
111         throws IOException
112     {
113 
114          this(filename,append,retainDays,zone,null,null);
115     }
116      
117     /* ------------------------------------------------------------ */
118     /**
119      * @param filename The filename must include the string "yyyy_mm_dd", 
120      * which is replaced with the actual date when creating and rolling over the file.
121      * @param append If true, existing files will be appended to.
122      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
123      * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd". 
124      * @param backupFormat The format for the file extension of backup files. The 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=ROLLOVER_FILE_DATE_FORMAT;
139         _fileDateFormat = new SimpleDateFormat(dateFormat);
140         
141         if (backupFormat==null)
142             backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
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(RolloverFileOutputStream.class.getName(),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     @Override
279     public void write (byte[] buf)
280             throws IOException
281      {
282             out.write (buf);
283      }
284 
285     /* ------------------------------------------------------------ */
286     @Override
287     public void write (byte[] buf, int off, int len)
288             throws IOException
289      {
290             out.write (buf, off, len);
291      }
292     
293     /* ------------------------------------------------------------ */
294     /** 
295      */
296     @Override
297     public void close()
298         throws IOException
299     {
300         synchronized(RolloverFileOutputStream.class)
301         {
302             try{super.close();}
303             finally
304             {
305                 out=null;
306                 _file=null;
307             }
308 
309             _rollTask.cancel(); 
310         }
311     }
312     
313     /* ------------------------------------------------------------ */
314     /* ------------------------------------------------------------ */
315     /* ------------------------------------------------------------ */
316     private class RollTask extends TimerTask
317     {
318         @Override
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 }