View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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  public class RolloverFileOutputStream extends FilterOutputStream
45  {
46      private static Timer __rollover;
47      
48      final static String YYYY_MM_DD="yyyy_mm_dd";
49      final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
50      final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
51      final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
52  
53      private RollTask _rollTask;
54      private SimpleDateFormat _fileBackupFormat;
55      private SimpleDateFormat _fileDateFormat;
56  
57      private String _filename;
58      private File _file;
59      private boolean _append;
60      private int _retainDays;
61      
62      /* ------------------------------------------------------------ */
63      /**
64       * @param filename The filename must include the string "yyyy_mm_dd", 
65       * which is replaced with the actual date when creating and rolling over the file.
66       * @throws IOException if unable to create output
67       */
68      public RolloverFileOutputStream(String filename)
69          throws IOException
70      {
71          this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
72      }
73      
74      /* ------------------------------------------------------------ */
75      /**
76       * @param filename The filename must include the string "yyyy_mm_dd", 
77       * which is replaced with the actual date when creating and rolling over the file.
78       * @param append If true, existing files will be appended to.
79       * @throws IOException if unable to create output
80       */
81      public RolloverFileOutputStream(String filename, boolean append)
82          throws IOException
83      {
84          this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
85      }
86  
87      /* ------------------------------------------------------------ */
88      /**
89       * @param filename The filename must include the string "yyyy_mm_dd", 
90       * which is replaced with the actual date when creating and rolling over the file.
91       * @param append If true, existing files will be appended to.
92       * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
93       * @throws IOException if unable to create output
94       */
95      public RolloverFileOutputStream(String filename,
96                                      boolean append,
97                                      int retainDays)
98          throws IOException
99      {
100         this(filename,append,retainDays,TimeZone.getDefault());
101     }
102 
103     /* ------------------------------------------------------------ */
104     /**
105      * @param filename The filename must include the string "yyyy_mm_dd", 
106      * which is replaced with the actual date when creating and rolling over the file.
107      * @param append If true, existing files will be appended to.
108      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
109      * @param zone the timezone for the output
110      * @throws IOException if unable to create output
111      */
112     public RolloverFileOutputStream(String filename,
113                                     boolean append,
114                                     int retainDays,
115                                     TimeZone zone)
116         throws IOException
117     {
118 
119          this(filename,append,retainDays,zone,null,null);
120     }
121      
122     /* ------------------------------------------------------------ */
123     /**
124      * @param filename The filename must include the string "yyyy_mm_dd", 
125      * which is replaced with the actual date when creating and rolling over the file.
126      * @param append If true, existing files will be appended to.
127      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
128      * @param zone the timezone for the output
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 if unable to create output
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 }