View Javadoc

    1   //========================================================================
    2   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
    3   //------------------------------------------------------------------------
    4   //Licensed under the Apache License, Version 2.0 (the "License");
    5   //you may not use this file except in compliance with the License.
    6   //You may obtain a copy of the License at
    7   //http://www.apache.org/licenses/LICENSE-2.0
    8   //Unless required by applicable law or agreed to in writing, software
    9   //distributed under the License is distributed on an "AS IS" BASIS,
    10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  //See the License for the specific language governing permissions and
    12  //limitations under the License.
    13  //========================================================================
    14
    15  package org.mortbay.util;
    16
    17  import java.io.File;
    18  import java.io.FileOutputStream;
    19  import java.io.FilterOutputStream;
    20  import java.io.IOException;
    21  import java.io.OutputStream;
    22  import java.lang.ref.WeakReference;
    23  import java.text.SimpleDateFormat;
    24  import java.util.ArrayList;
    25  import java.util.Calendar;
    26  import java.util.Date;
    27  import java.util.GregorianCalendar;
    28  import java.util.ListIterator;
    29  import java.util.StringTokenizer;
    30  import java.util.TimeZone;
    31  import java.util.Timer;
    32  import java.util.TimerTask;
    33
    34  /**
    35   * RolloverFileOutputStream
    36   *
    37   * This output stream puts content in a file that is rolled over every 24 hours.
    38   * The filename must include the string "yyyy_mm_dd", which is replaced with the
    39   * actual date when creating and rolling over the file.
    40   *
    41   * Old files are retained for a number of days before being deleted.
    42   *
    43   * @author Greg Wilkins
    44   */
    45  public class RolloverFileOutputStream extends FilterOutputStream
    46  {
    47      private static Timer __rollover;
    48
    49      final static String YYYY_MM_DD="yyyy_mm_dd";
    50
    51      private RollTask _rollTask;
    52      private SimpleDateFormat _fileBackupFormat;
    53      private SimpleDateFormat _fileDateFormat;
    54
    55      private String _filename;
    56      private File _file;
    57      private boolean _append;
    58      private int _retainDays;
    59
    60      /* ------------------------------------------------------------ */
    61      /**
    62       * @param filename The filename must include the string "yyyy_mm_dd",
    63       * which is replaced with the actual date when creating and rolling over the file.
    64       * @throws IOException
    65       */
    66      public RolloverFileOutputStream(String filename)
    67          throws IOException
    68      {
    69          this(filename,true,Integer.getInteger("ROLLOVERFILE_RETAIN_DAYS",31).intValue());
    70      }
    71
    72      /* ------------------------------------------------------------ */
    73      /**
    74       * @param filename The filename must include the string "yyyy_mm_dd",
    75       * which is replaced with the actual date when creating and rolling over the file.
    76       * @param append If true, existing files will be appended to.
    77       * @throws IOException
    78       */
    79      public RolloverFileOutputStream(String filename, boolean append)
    80          throws IOException
    81      {
    82          this(filename,append,Integer.getInteger("ROLLOVERFILE_RETAIN_DAYS",31).intValue());
    83      }
    84
    85      /* ------------------------------------------------------------ */
    86      /**
    87       * @param filename The filename must include the string "yyyy_mm_dd",
    88       * which is replaced with the actual date when creating and rolling over the file.
    89       * @param append If true, existing files will be appended to.
    90       * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
    91       * @throws IOException
    92       */
    93      public RolloverFileOutputStream(String filename,
    94                                      boolean append,
    95                                      int retainDays)
    96          throws IOException
    97      {
    98          this(filename,append,retainDays,TimeZone.getDefault());
    99      }
    100
    101     /* ------------------------------------------------------------ */
    102     /**
    103      * @param filename The filename must include the string "yyyy_mm_dd",
    104      * which is replaced with the actual date when creating and rolling over the file.
    105      * @param append If true, existing files will be appended to.
    106      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
    107      * @throws IOException
    108      */
    109     public RolloverFileOutputStream(String filename,
    110                                     boolean append,
    111                                     int retainDays,
    112                                     TimeZone zone)
    113         throws IOException
    114     {
    115
    116          this(filename,append,retainDays,zone,null,null);
    117     }
    118
    119     /* ------------------------------------------------------------ */
    120     /**
    121      * @param filename The filename must include the string "yyyy_mm_dd",
    122      * which is replaced with the actual date when creating and rolling over the file.
    123      * @param append If true, existing files will be appended to.
    124      * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
    125      * @param dateFormat The format for the date file substitution. If null the system property ROLLOVERFILE_DATE_FORMAT is
    126      * used and if that is null, then default is "yyyy_MM_dd".
    127      * @param backupFormat The format for the file extension of backup files. If null the system property
    128      * ROLLOVERFILE_BACKUP_FORMAT is used and if that is null, then default is "HHmmssSSS".
    129      * @throws IOException
    130      */
    131     public RolloverFileOutputStream(String filename,
    132                                     boolean append,
    133                                     int retainDays,
    134                                     TimeZone zone,
    135                                     String dateFormat,
    136                                     String backupFormat)
    137         throws IOException
    138     {
    139         super(null);
    140
    141         if (dateFormat==null)
    142             dateFormat=System.getProperty("ROLLOVERFILE_DATE_FORMAT","yyyy_MM_dd");
    143         _fileDateFormat = new SimpleDateFormat(dateFormat);
    144
    145         if (backupFormat==null)
    146             backupFormat=System.getProperty("ROLLOVERFILE_BACKUP_FORMAT","HHmmssSSS");
    147         _fileBackupFormat = new SimpleDateFormat(backupFormat);
    148
    149         _fileBackupFormat.setTimeZone(zone);
    150         _fileDateFormat.setTimeZone(zone);
    151
    152         if (filename!=null)
    153         {
    154             filename=filename.trim();
    155             if (filename.length()==0)
    156                 filename=null;
    157         }
    158         if (filename==null)
    159             throw new IllegalArgumentException("Invalid filename");
    160
    161         _filename=filename;
    162         _append=append;
    163         _retainDays=retainDays;
    164         setFile();
    165
    166         synchronized(RolloverFileOutputStream.class)
    167         {
    168             if (__rollover==null)
    169                 __rollover=new Timer(true);
    170
    171             _rollTask=new RollTask();
    172
    173              Calendar now = Calendar.getInstance();
    174              now.setTimeZone(zone);
    175
    176              GregorianCalendar midnight =
    177                  new GregorianCalendar(now.get(Calendar.YEAR),
    178                          now.get(Calendar.MONTH),
    179                          now.get(Calendar.DAY_OF_MONTH),
    180                          23,0);
    181              midnight.setTimeZone(zone);
    182              midnight.add(Calendar.HOUR,1);
    183              __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
    184         }
    185     }
    186
    187     /* ------------------------------------------------------------ */
    188     public String getFilename()
    189     {
    190         return _filename;
    191     }
    192
    193     /* ------------------------------------------------------------ */
    194     public String getDatedFilename()
    195     {
    196         if (_file==null)
    197             return null;
    198         return _file.toString();
    199     }
    200
    201     /* ------------------------------------------------------------ */
    202     public int getRetainDays()
    203     {
    204         return _retainDays;
    205     }
    206
    207     /* ------------------------------------------------------------ */
    208     private synchronized void setFile()
    209         throws IOException
    210     {
    211         // Check directory
    212         File file = new File(_filename);
    213         _filename=file.getCanonicalPath();
    214         file=new File(_filename);
    215         File dir= new File(file.getParent());
    216         if (!dir.isDirectory() || !dir.canWrite())
    217             throw new IOException("Cannot write log directory "+dir);
    218
    219         Date now=new Date();
    220
    221         // Is this a rollover file?
    222         String filename=file.getName();
    223         int i=filename.toLowerCase().indexOf(YYYY_MM_DD);
    224         if (i>=0)
    225         {
    226             file=new File(dir,
    227                           filename.substring(0,i)+
    228                           _fileDateFormat.format(now)+
    229                           filename.substring(i+YYYY_MM_DD.length()));
    230         }
    231
    232         if (file.exists()&&!file.canWrite())
    233             throw new IOException("Cannot write log file "+file);
    234
    235         // Do we need to change the output stream?
    236         if (out==null || !file.equals(_file))
    237         {
    238             // Yep
    239             _file=file;
    240             if (!_append && file.exists())
    241                 file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
    242             OutputStream oldOut=out;
    243             out=new FileOutputStream(file.toString(),_append);
    244             if (oldOut!=null)
    245                 oldOut.close();
    246             //if(log.isDebugEnabled())log.debug("Opened "+_file);
    247         }
    248     }
    249
    250     /* ------------------------------------------------------------ */
    251     private void removeOldFiles()
    252     {
    253         if (_retainDays>0)
    254         {
    255             long now = System.currentTimeMillis();
    256
    257             File file= new File(_filename);
    258             File dir = new File(file.getParent());
    259             String fn=file.getName();
    260             int s=fn.toLowerCase().indexOf(YYYY_MM_DD);
    261             if (s<0)
    262                 return;
    263             String prefix=fn.substring(0,s);
    264             String suffix=fn.substring(s+YYYY_MM_DD.length());
    265
    266             String[] logList=dir.list();
    267             for (int i=0;i<logList.length;i++)
    268             {
    269                 fn = logList[i];
    270                 if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
    271                 {
    272                     File f = new File(dir,fn);
    273                     long date = f.lastModified();
    274                     if ( ((now-date)/(1000*60*60*24))>_retainDays)
    275                         f.delete();
    276                 }
    277             }
    278         }
    279     }
    280
    281     /* ------------------------------------------------------------ */
    282     public void write (byte[] buf)
    283             throws IOException
    284      {
    285             out.write (buf);
    286      }
    287
    288     /* ------------------------------------------------------------ */
    289     public void write (byte[] buf, int off, int len)
    290             throws IOException
    291      {
    292             out.write (buf, off, len);
    293      }
    294
    295     /* ------------------------------------------------------------ */
    296     /**
    297      */
    298     public void close()
    299         throws IOException
    300     {
    301         synchronized(RolloverFileOutputStream.class)
    302         {
    303             try{super.close();}
    304             finally
    305             {
    306                 out=null;
    307                 _file=null;
    308             }
    309
    310             _rollTask.cancel();
    311         }
    312     }
    313
    314     /* ------------------------------------------------------------ */
    315     /* ------------------------------------------------------------ */
    316     /* ------------------------------------------------------------ */
    317     private class RollTask extends TimerTask
    318     {
    319         public void run()
    320         {
    321             try
    322             {
    323                 RolloverFileOutputStream.this.setFile();
    324                 RolloverFileOutputStream.this.removeOldFiles();
    325
    326             }
    327             catch(IOException e)
    328             {
    329                 e.printStackTrace();
    330             }
    331         }
    332     }
    333 }