2 // ========================================================================
3 // Copyright (c) 1995-2014 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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.util;
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;
36 * RolloverFileOutputStream
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.
42 * Old files are retained for a number of days before being deleted.
46 public class RolloverFileOutputStream extends FilterOutputStream
48 private static Timer __rollover;
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;
55 private RollTask _rollTask;
56 private SimpleDateFormat _fileBackupFormat;
57 private SimpleDateFormat _fileDateFormat;
59 private String _filename;
61 private boolean _append;
62 private int _retainDays;
64 /* ------------------------------------------------------------ */
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.
70 public RolloverFileOutputStream(String filename)
73 this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
76 /* ------------------------------------------------------------ */
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.
83 public RolloverFileOutputStream(String filename, boolean append)
86 this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
89 /* ------------------------------------------------------------ */
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.
97 public RolloverFileOutputStream(String filename,
102 this(filename,append,retainDays,TimeZone.getDefault());
105 /* ------------------------------------------------------------ */
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
113 public RolloverFileOutputStream(String filename,
120 this(filename,append,retainDays,zone,null,null);
123 /* ------------------------------------------------------------ */
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
133 public RolloverFileOutputStream(String filename,
143 if (dateFormat==null)
144 dateFormat=ROLLOVER_FILE_DATE_FORMAT;
145 _fileDateFormat = new SimpleDateFormat(dateFormat);
147 if (backupFormat==null)
148 backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
149 _fileBackupFormat = new SimpleDateFormat(backupFormat);
151 _fileBackupFormat.setTimeZone(zone);
152 _fileDateFormat.setTimeZone(zone);
156 filename=filename.trim();
157 if (filename.length()==0)
161 throw new IllegalArgumentException("Invalid filename");
165 _retainDays=retainDays;
168 synchronized(RolloverFileOutputStream.class)
170 if (__rollover==null)
171 __rollover=new Timer(RolloverFileOutputStream.class.getName(),true);
173 _rollTask=new RollTask();
175 Calendar now = Calendar.getInstance();
176 now.setTimeZone(zone);
178 GregorianCalendar midnight =
179 new GregorianCalendar(now.get(Calendar.YEAR),
180 now.get(Calendar.MONTH),
181 now.get(Calendar.DAY_OF_MONTH),
183 midnight.setTimeZone(zone);
184 midnight.add(Calendar.HOUR,1);
185 __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
189 /* ------------------------------------------------------------ */
190 public String getFilename()
195 /* ------------------------------------------------------------ */
196 public String getDatedFilename()
200 return _file.toString();
203 /* ------------------------------------------------------------ */
204 public int getRetainDays()
209 /* ------------------------------------------------------------ */
210 private synchronized void setFile()
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);
223 // Is this a rollover file?
224 String filename=file.getName();
225 int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
229 filename.substring(0,i)+
230 _fileDateFormat.format(now)+
231 filename.substring(i+YYYY_MM_DD.length()));
234 if (file.exists()&&!file.canWrite())
235 throw new IOException("Cannot write log file "+file);
237 // Do we need to change the output stream?
238 if (out==null || !file.equals(_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);
248 //if(log.isDebugEnabled())log.debug("Opened "+_file);
252 /* ------------------------------------------------------------ */
253 private void removeOldFiles()
257 long now = System.currentTimeMillis();
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);
265 String prefix=fn.substring(0,s);
266 String suffix=fn.substring(s+YYYY_MM_DD.length());
268 String[] logList=dir.list();
269 for (int i=0;i<logList.length;i++)
272 if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
274 File f = new File(dir,fn);
275 long date = f.lastModified();
276 if ( ((now-date)/(1000*60*60*24))>_retainDays)
283 /* ------------------------------------------------------------ */
285 public void write (byte[] buf)
291 /* ------------------------------------------------------------ */
293 public void write (byte[] buf, int off, int len)
296 out.write (buf, off, len);
299 /* ------------------------------------------------------------ */
306 synchronized(RolloverFileOutputStream.class)
319 /* ------------------------------------------------------------ */
320 /* ------------------------------------------------------------ */
321 /* ------------------------------------------------------------ */
322 private class RollTask extends TimerTask
329 RolloverFileOutputStream.this.setFile();
330 RolloverFileOutputStream.this.removeOldFiles();
335 // Cannot log this exception to a LOG, as RolloverFOS can be used by logging