View Javadoc

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