View Javadoc

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