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