1 // 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. 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 */ RolloverFileOutputStream(String filename)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 */ RolloverFileOutputStream(String filename, boolean append)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 */ RolloverFileOutputStream(String filename, boolean append, int retainDays)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 */ RolloverFileOutputStream(String filename, boolean append, int retainDays, TimeZone zone)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 */ RolloverFileOutputStream(String filename, boolean append, int retainDays, TimeZone zone, String dateFormat, String backupFormat)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 /* ------------------------------------------------------------ */ getFilename()190 public String getFilename() 191 { 192 return _filename; 193 } 194 195 /* ------------------------------------------------------------ */ getDatedFilename()196 public String getDatedFilename() 197 { 198 if (_file==null) 199 return null; 200 return _file.toString(); 201 } 202 203 /* ------------------------------------------------------------ */ getRetainDays()204 public int getRetainDays() 205 { 206 return _retainDays; 207 } 208 209 /* ------------------------------------------------------------ */ setFile()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 /* ------------------------------------------------------------ */ removeOldFiles()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 write(byte[] buf)285 public void write (byte[] buf) 286 throws IOException 287 { 288 out.write (buf); 289 } 290 291 /* ------------------------------------------------------------ */ 292 @Override write(byte[] buf, int off, int len)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 close()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 run()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 } 341