KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > log > AbstractRolloverLog


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.log;
31
32 import com.caucho.config.ConfigException;
33 import com.caucho.config.types.Bytes;
34 import com.caucho.config.types.Period;
35 import com.caucho.util.Alarm;
36 import com.caucho.util.L10N;
37 import com.caucho.util.QDate;
38 import com.caucho.util.ThreadPool;
39 import com.caucho.vfs.Path;
40 import com.caucho.vfs.ReadStream;
41 import com.caucho.vfs.TempStream;
42 import com.caucho.vfs.Vfs;
43 import com.caucho.vfs.WriteStream;
44
45 import java.io.IOException JavaDoc;
46 import java.io.OutputStream JavaDoc;
47 import java.util.ArrayList JavaDoc;
48 import java.util.Collections JavaDoc;
49 import java.util.regex.Matcher JavaDoc;
50 import java.util.regex.Pattern JavaDoc;
51 import java.util.zip.GZIPOutputStream JavaDoc;
52 import java.util.zip.ZipOutputStream JavaDoc;
53
54 /**
55  * Abstract class for a log that rolls over based on size or period.
56  */

57 public class AbstractRolloverLog {
58   protected static final L10N L = new L10N(AbstractRolloverLog.class);
59
60   // Milliseconds in an hour
61
private static final long HOUR = 3600L * 1000L;
62   // Milliseconds in a day
63
private static final long DAY = 24L * 3600L * 1000L;
64
65   // Default maximum log size = 2G
66
private static final long DEFAULT_ROLLOVER_SIZE = Bytes.INFINITE;
67   // How often to check size
68
private static final long DEFAULT_ROLLOVER_CHECK_PERIOD = 600L * 1000L;
69
70   // prefix for the rollover
71
private String JavaDoc _rolloverPrefix;
72
73   // template for the archived files
74
private String JavaDoc _archiveFormat;
75   // .gz or .zip
76
private String JavaDoc _archiveSuffix = "";
77   
78   // How often the logs are rolled over.
79
private long _rolloverPeriod = Period.INFINITE;
80
81   // Maximum size of the log.
82
private long _rolloverSize = DEFAULT_ROLLOVER_SIZE;
83
84   // How often the rolloverSize should be checked
85
private long _rolloverCheckPeriod = DEFAULT_ROLLOVER_CHECK_PERIOD;
86
87   // How many archives are allowed.
88
private int _rolloverCount;
89
90   private QDate _calendar = QDate.createLocal();
91
92   private Path _pwd = Vfs.lookup();
93   
94   protected Path _path;
95
96   protected String JavaDoc _pathFormat;
97
98   private String JavaDoc _format;
99
100   // The time of the next period-based rollover
101
private long _nextPeriodEnd = -1;
102   private long _nextRolloverCheckTime = -1;
103
104   private boolean _isRollingOver;
105   private Path _savedPath;
106   private TempStream _tempStream;
107   private ArchiveTask _archiveTask = new ArchiveTask();
108
109   private WriteStream _os;
110   private WriteStream _zipOut;
111
112   /**
113    * Returns the access-log's path.
114    */

115   public Path getPath()
116   {
117     return _path;
118   }
119
120   /**
121    * Sets the access-log's path.
122    */

123   public void setPath(Path path)
124   {
125     _path = path;
126   }
127
128   /**
129    * Returns the pwd for the rollover log
130    */

131   public Path getPwd()
132   {
133     return _pwd;
134   }
135
136   /**
137    * Returns the formatted path
138    */

139   public String JavaDoc getPathFormat()
140   {
141     return _pathFormat;
142   }
143
144   /**
145    * Sets the formatted path.
146    */

147   public void setPathFormat(String JavaDoc pathFormat)
148     throws ConfigException
149   {
150     _pathFormat = pathFormat;
151     
152     if (pathFormat.endsWith(".zip")) {
153       throw new ConfigException(L.l(".zip extension to path-format is not supported."));
154     }
155   }
156
157   /**
158    * Sets the archive name format
159    */

160   public void setArchiveFormat(String JavaDoc format)
161   {
162     if (format.endsWith(".gz")) {
163       _archiveFormat = format.substring(0, format.length() - ".gz".length());
164       _archiveSuffix = ".gz";
165     }
166     else if (format.endsWith(".zip")) {
167       _archiveFormat = format.substring(0, format.length() - ".zip".length());
168       _archiveSuffix = ".zip";
169     }
170     else {
171       _archiveFormat = format;
172       _archiveSuffix = "";
173     }
174   }
175
176   /**
177    * Sets the archive name format
178    */

179   public String JavaDoc getArchiveFormat()
180   {
181     if (_archiveFormat == null)
182       return _rolloverPrefix + ".%Y%m%d.%H%M";
183     else
184       return _archiveFormat;
185   }
186
187   /**
188    * Sets the log rollover period, rounded up to the nearest hour.
189    *
190    * @param period the new rollover period in milliseconds.
191    */

192   public void setRolloverPeriod(Period period)
193   {
194     _rolloverPeriod = period.getPeriod();
195     
196     if (_rolloverPeriod > 0) {
197       _rolloverPeriod += 3600000L - 1;
198       _rolloverPeriod -= _rolloverPeriod % 3600000L;
199     }
200     else
201       _rolloverPeriod = Period.INFINITE;
202   }
203
204   /**
205    * Sets the log rollover period, rounded up to the nearest hour.
206    *
207    * @return the new period in milliseconds.
208    */

209   public long getRolloverPeriod()
210   {
211     return _rolloverPeriod;
212   }
213
214   /**
215    * Sets the log rollover size, rounded up to the megabyte.
216    *
217    * @param bytes maximum size of the log file
218    */

219   public void setRolloverSize(Bytes bytes)
220   {
221     long size = bytes.getBytes();
222     
223     if (size < 0)
224       _rolloverSize = Bytes.INFINITE;
225     else
226       _rolloverSize = size;
227   }
228
229   /**
230    * Sets the log rollover size, rounded up to the megabyte.
231    *
232    * @return maximum size of the log file
233    */

234   public long getRolloverSize()
235   {
236     return _rolloverSize;
237   }
238
239   /**
240    * Sets how often the log rollover will be checked.
241    *
242    * @param period how often the log rollover will be checked.
243    */

244   public void setRolloverCheckPeriod(long period)
245   {
246     if (period > 1000)
247       _rolloverCheckPeriod = period;
248     else if (period > 0)
249       _rolloverCheckPeriod = 1000;
250   }
251
252   /**
253    * Sets how often the log rollover will be checked.
254    *
255    * @return how often the log rollover will be checked.
256    */

257   public long getRolloverCheckPeriod()
258   {
259     return _rolloverCheckPeriod;
260   }
261
262   /**
263    * Sets the max rollover files.
264    */

265   public void setRolloverCount(int count)
266   {
267     _rolloverCount = count;
268   }
269   
270   /**
271    * Initialize the log.
272    */

273   public void init()
274     throws IOException JavaDoc
275   {
276     long now = Alarm.getCurrentTime();
277     
278     _nextRolloverCheckTime = now + _rolloverCheckPeriod;
279
280     Path path = getPath();
281
282     if (path != null) {
283       path.getParent().mkdirs();
284     
285       _rolloverPrefix = path.getTail();
286
287       long lastModified = path.getLastModified();
288       if (lastModified <= 0)
289     lastModified = now;
290     
291       _calendar.setGMTTime(lastModified);
292
293       _nextPeriodEnd = Period.periodEnd(lastModified, getRolloverPeriod());
294     }
295     else
296       _nextPeriodEnd = Period.periodEnd(now, getRolloverPeriod());
297
298     if (_nextPeriodEnd < _nextRolloverCheckTime && _nextPeriodEnd > 0)
299       _nextRolloverCheckTime = _nextPeriodEnd;
300
301     if (_archiveFormat != null || getRolloverPeriod() <= 0) {
302     }
303     else if (getRolloverPeriod() % DAY == 0)
304       _archiveFormat = _rolloverPrefix + ".%Y%m%d";
305     else if (getRolloverPeriod() % HOUR == 0)
306       _archiveFormat = _rolloverPrefix + ".%Y%m%d.%H";
307     else
308       _archiveFormat = _rolloverPrefix + ".%Y%m%d.%H%M";
309
310     rolloverLog(now);
311   }
312
313   public long getNextRolloverCheckTime()
314   {
315     if (_nextPeriodEnd < _nextRolloverCheckTime)
316       return _nextPeriodEnd;
317     else
318       return _nextRolloverCheckTime;
319   }
320
321   public boolean isRollover()
322   {
323     long now = Alarm.getCurrentTime();
324     
325     return _nextPeriodEnd <= now || _nextRolloverCheckTime <= now;
326   }
327
328   public boolean rollover()
329   {
330     long now = Alarm.getCurrentTime();
331
332     if (_nextPeriodEnd <= now || _nextRolloverCheckTime <= now) {
333       rolloverLog(now);
334       return true;
335     }
336     else
337       return false;
338   }
339
340   /**
341    * Writes to the underlying log.
342    */

343   protected void write(byte []buffer, int offset, int length)
344     throws IOException JavaDoc
345   {
346     synchronized (this) {
347       if (! _isRollingOver) {
348     if (_os == null)
349       openLog();
350
351     if (_os != null)
352       _os.write(buffer, offset, length);
353       }
354       else {
355     if (_tempStream == null)
356       _tempStream = new TempStream();
357
358     _tempStream.write(buffer, offset, length, false);
359       }
360     }
361   }
362
363   /**
364    * Writes to the underlying log.
365    */

366   protected void flush()
367     throws IOException JavaDoc
368   {
369     synchronized (this) {
370       if (_os != null)
371     _os.flush();
372     }
373   }
374
375   /**
376    * Check to see if we need to rollover the log.
377    *
378    * @param now current time in milliseconds.
379    */

380   protected void rolloverLog(long now)
381   {
382     boolean isRollingOver = false;
383     
384     try {
385       Path savedPath = null;
386
387       synchronized (this) {
388     if (_isRollingOver || now <= _nextRolloverCheckTime)
389       return;
390
391     _isRollingOver = isRollingOver = true;
392       
393     _nextRolloverCheckTime = now + _rolloverCheckPeriod;
394
395     long lastPeriodEnd = _nextPeriodEnd;
396     _nextPeriodEnd = Period.periodEnd(now, getRolloverPeriod());
397
398     Path path = getPath();
399
400     if (lastPeriodEnd < now) {
401       closeLogStream();
402       
403       if (getPathFormat() == null) {
404         savedPath = getArchivePath(lastPeriodEnd - 1);
405       }
406
407       /*
408         if (log.isLoggable(Level.FINE))
409         log.fine(getPath() + ": next rollover at " +
410         QDate.formatLocal(_nextPeriodEnd));
411       */

412     }
413     else if (path != null && getRolloverSize() <= path.getLength()) {
414       closeLogStream();
415       
416       if (getPathFormat() == null) {
417         savedPath = getArchivePath(now);
418       }
419     }
420
421     long nextPeriodEnd = _nextPeriodEnd;
422     if (_nextPeriodEnd < _nextRolloverCheckTime && _nextPeriodEnd > 0)
423       _nextRolloverCheckTime = _nextPeriodEnd;
424       }
425
426       // archiving of path is outside of the synchronized block to
427
// avoid freezing during archive
428
if (savedPath != null) {
429     _savedPath = savedPath;
430     isRollingOver = false;
431     ThreadPool.getThreadPool().start(_archiveTask);
432     Thread.yield();
433       }
434     } finally {
435       synchronized (this) {
436     if (isRollingOver) {
437       _isRollingOver = false;
438       flushTempStream();
439     }
440       }
441     }
442   }
443
444   /**
445    * Tries to open the log.
446    */

447   private void openLog()
448   {
449     closeLogStream();
450     
451     try {
452       WriteStream os = _os;
453       _os = null;
454
455       if (os != null)
456     os.close();
457     } catch (Throwable JavaDoc e) {
458       // can't log in log routines
459
}
460       
461     Path path = getPath();
462
463     if (path == null) {
464       path = getPath(Alarm.getCurrentTime());
465     }
466     
467     try {
468       if (! path.getParent().isDirectory())
469     path.getParent().mkdirs();
470     } catch (Throwable JavaDoc e) {
471       logWarning(L.l("Can't create log directory {0}", path.getParent()), e);
472     }
473
474     Exception JavaDoc exn = null;
475     
476     for (int i = 0; i < 3 && _os == null; i++) {
477       try {
478     _os = path.openAppend();
479       } catch (IOException JavaDoc e) {
480     exn = e;
481       }
482     }
483
484     String JavaDoc pathName = path.getPath();
485
486     try {
487       if (pathName.endsWith(".gz")) {
488     _zipOut = _os;
489     _os = Vfs.openWrite(new GZIPOutputStream JavaDoc(_zipOut));
490       }
491       else if (pathName.endsWith(".zip")) {
492     throw new ConfigException("Can't support .zip in path-format");
493       }
494     } catch (Exception JavaDoc e) {
495       if (exn == null)
496     exn = e;
497     }
498
499     if (exn != null)
500       logWarning(L.l("Can't create log directory {0}", path), exn);
501   }
502
503   private void movePathToArchive(Path savedPath)
504   {
505     if (savedPath == null)
506       return;
507     
508     closeLogStream();
509     
510     Path path = getPath();
511     
512     String JavaDoc savedName = savedPath.getTail();
513
514     logInfo(L.l("Archiving access log to {0}.", savedName));
515          
516     try {
517       WriteStream os = _os;
518       _os = null;
519       if (os != null)
520     os.close();
521     } catch (IOException JavaDoc e) {
522       // can't log in log routines
523
}
524
525     try {
526       if (! savedPath.getParent().isDirectory())
527     savedPath.getParent().mkdirs();
528     } catch (Throwable JavaDoc e) {
529       logWarning(L.l("Can't open archive directory {0}",
530              savedPath.getParent()),
531          e);
532     }
533         
534     try {
535       WriteStream os = savedPath.openWrite();
536       OutputStream JavaDoc out;
537
538       if (savedName.endsWith(".gz"))
539     out = new GZIPOutputStream JavaDoc(os);
540       else if (savedName.endsWith(".zip"))
541     out = new ZipOutputStream JavaDoc(os);
542       else
543     out = os;
544
545       try {
546     path.writeToStream(out);
547       } finally {
548     try {
549       out.close();
550     } catch (Throwable JavaDoc e) {
551       // can't log in log rotation routines
552
}
553
554     try {
555       if (out != os)
556         os.close();
557     } catch (Throwable JavaDoc e) {
558       // can't log in log rotation routines
559
}
560
561     try {
562       if (! path.truncate())
563         path.remove();
564     } catch (IOException JavaDoc e) {
565       path.remove();
566
567       throw e;
568     }
569       }
570     } catch (Throwable JavaDoc e) {
571       logWarning(L.l("Error rotating logs"), e);
572     }
573
574     if (_rolloverCount > 0)
575       removeOldLogs();
576   }
577
578   /**
579    * Removes logs passing the rollover count.
580    */

581   private void removeOldLogs()
582   {
583     try {
584       Path path = getPath();
585       Path parent = path.getParent();
586
587       String JavaDoc []list = parent.list();
588
589       ArrayList JavaDoc<String JavaDoc> matchList = new ArrayList JavaDoc<String JavaDoc>();
590
591       Pattern JavaDoc archiveRegexp = getArchiveRegexp();
592       for (int i = 0; i < list.length; i++) {
593     Matcher JavaDoc matcher = archiveRegexp.matcher(list[i]);
594
595     if (matcher.matches())
596       matchList.add(list[i]);
597       }
598
599       Collections.sort(matchList);
600
601       if (_rolloverCount <= 0 || matchList.size() < _rolloverCount)
602     return;
603
604       for (int i = 0; i + _rolloverCount < matchList.size(); i++) {
605     try {
606       parent.lookup(matchList.get(i)).remove();
607     } catch (Throwable JavaDoc e) {
608     }
609       }
610     } catch (Throwable JavaDoc e) {
611     }
612   }
613
614   private Pattern JavaDoc getArchiveRegexp()
615   {
616     StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
617
618     String JavaDoc archiveFormat = getArchiveFormat();
619
620     for (int i = 0; i < archiveFormat.length(); i++) {
621       char ch = archiveFormat.charAt(i);
622
623       switch (ch) {
624       case '.': case '\\': case '*': case '?': case '+':
625       case '(': case ')': case '{': case '}': case '|':
626     sb.append("\\");
627     sb.append(ch);
628     break;
629       case '%':
630     sb.append(".+");
631     i++;
632     break;
633       default:
634     sb.append(ch);
635     break;
636       }
637     }
638
639     return Pattern.compile(sb.toString());
640   }
641
642   /**
643    * Returns the path of the format file
644    *
645    * @param time the archive date
646    */

647   protected Path getPath(long time)
648   {
649     String JavaDoc formatString = getPathFormat();
650
651     if (formatString == null)
652       throw new IllegalStateException JavaDoc(L.l("getPath requires a format path"));
653     
654     String JavaDoc pathString = getFormatName(formatString, time);
655
656     return getPwd().lookup(pathString);
657   }
658
659   /**
660    * Returns the name of the archived file
661    *
662    * @param time the archive date
663    */

664   protected Path getArchivePath(long time)
665   {
666     Path path = getPath();
667
668     String JavaDoc archiveFormat = getArchiveFormat();
669
670     String JavaDoc name = getFormatName(archiveFormat + _archiveSuffix, time);
671     Path newPath = path.getParent().lookup(name);
672
673     if (newPath.exists()) {
674       if (archiveFormat.indexOf("%H") < 0)
675     archiveFormat = archiveFormat + ".%H%M";
676       else if (archiveFormat.indexOf("%M") < 0)
677     archiveFormat = archiveFormat + ".%M";
678
679       for (int i = 0; i < 100; i++) {
680     String JavaDoc suffix;
681
682     if (i == 0)
683       suffix = _archiveSuffix;
684     else
685       suffix = "." + i + _archiveSuffix;
686     
687     name = getFormatName(archiveFormat + suffix, time);
688
689     newPath = path.getParent().lookup(name);
690
691     if (! newPath.exists())
692       break;
693       }
694     }
695
696     return newPath;
697   }
698
699   /**
700    * Returns the name of the archived file
701    *
702    * @param time the archive date
703    */

704   protected String JavaDoc getFormatName(String JavaDoc format, long time)
705   {
706     if (time <= 0)
707       time = Alarm.getCurrentTime();
708     
709     if (format != null)
710       return _calendar.formatLocal(time, format);
711     else if (getRolloverPeriod() % (24 * 3600 * 1000L) == 0)
712       return _rolloverPrefix + "." + _calendar.formatLocal(time, "%Y%m%d");
713     else
714       return _rolloverPrefix + "." + _calendar.formatLocal(time, "%Y%m%d.%H");
715   }
716
717   /**
718    * error messages from the log itself
719    */

720   private void logInfo(String JavaDoc msg)
721   {
722     EnvironmentStream.logStderr(msg);
723   }
724
725   /**
726    * error messages from the log itself
727    */

728   private void logWarning(String JavaDoc msg, Throwable JavaDoc e)
729   {
730     EnvironmentStream.logStderr(msg, e);
731   }
732
733   /**
734    * Closes the log, flushing the results.
735    */

736   public synchronized void close()
737     throws IOException JavaDoc
738   {
739     closeLogStream();
740   }
741
742   /**
743    * Tries to close the log.
744    */

745   private void closeLogStream()
746   {
747     try {
748       WriteStream os = _os;
749       _os = null;
750
751       if (os != null)
752     os.close();
753     } catch (Throwable JavaDoc e) {
754       // can't log in log routines
755
}
756
757     try {
758       WriteStream zipOut = _zipOut;
759       _zipOut = null;
760
761       if (zipOut != null)
762     zipOut.close();
763     } catch (Throwable JavaDoc e) {
764       // can't log in log routines
765
}
766   }
767
768   void flushTempStream()
769   {
770     TempStream ts = _tempStream;
771     _tempStream = null;
772     
773     if (ts != null) {
774       if (_os == null)
775     openLog();
776
777       try {
778     ReadStream is = ts.openRead(true);
779
780     try {
781       is.writeToStream(_os);
782     } finally {
783       is.close();
784     }
785       } catch (IOException JavaDoc e) {
786     e.printStackTrace();
787       }
788     }
789   }
790
791   class ArchiveTask implements Runnable JavaDoc {
792     public void run()
793     {
794       try {
795     movePathToArchive(_savedPath);
796       } finally {
797     // Write any new data from the temp stream to the log.
798
synchronized (this) {
799       _isRollingOver = false;
800
801       flushTempStream();
802     }
803       }
804     }
805   }
806 }
807
Popular Tags