tomcat-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jfarc...@apache.org
Subject cvs commit: jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/valves ByteBufferAccessLogValve.java mbeans-descriptors.xml
Date Fri, 19 Nov 2004 16:46:27 GMT
jfarcand    2004/11/19 08:46:27

  Modified:    catalina/src/conf server.xml
               catalina/src/share/org/apache/catalina/valves
                        mbeans-descriptors.xml
  Added:       catalina/src/share/org/apache/catalina/valves
                        ByteBufferAccessLogValve.java
  Log:
  Add an asynchronous byte buffer based access log valve. The valve use a direct byte buffer
to store and save the log. I didn't use the backgroundProcess since it's affect all components,
but this should be revisited.
  
  Benchmarking this valve using jservlet (scalabilty) and trade2 (throughput) demonstrated
a 13% improvement over the current access log valves. Increasing the writerInterval improve
performance too.
  
  Note: I've tried to use a direct CharBuffer instead of a StringBuffer, and its slower that
the current approach (and the log contains garbage, thank to a bug in NIO).
  
  Revision  Changes    Path
  1.43      +16 -0     jakarta-tomcat-catalina/catalina/src/conf/server.xml
  
  Index: server.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-catalina/catalina/src/conf/server.xml,v
  retrieving revision 1.42
  retrieving revision 1.43
  diff -u -r1.42 -r1.43
  --- server.xml	29 Oct 2004 18:49:24 -0000	1.42
  +++ server.xml	19 Nov 2004 16:46:26 -0000	1.43
  @@ -351,6 +351,22 @@
                    directory="logs"  prefix="localhost_access_log." suffix=".txt"
                    pattern="common" resolveHosts="false"/>
           -->
  +        <!-- Access log processes all requests for this virtual host.  By
  +             default, log files are created in the "logs" directory relative to
  +             $CATALINA_HOME.  If you wish, you can specify a different
  +             directory with the "directory" attribute.  Specify either a relative
  +             (to $CATALINA_HOME) or absolute path to the desired directory.
  +             This access log implementation is optimized for maximum performance,
  +             but is hardcoded to support only the "common" and "combined" patterns.
  +
  +             This valve use NIO direct Byte Buffer to asynchornously store the
  +             log.
  +        -->
  +        <!--
  +        <Valve className="org.apache.catalina.valves.ByteBufferAccessLogValve"
  +                 directory="logs"  prefix="localhost_access_log." suffix=".txt"
  +                 pattern="common" resolveHosts="false"/>
  +        -->
   
         </Host>
   
  
  
  
  1.8       +61 -0     jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/valves/mbeans-descriptors.xml
  
  Index: mbeans-descriptors.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/valves/mbeans-descriptors.xml,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- mbeans-descriptors.xml	25 Oct 2004 15:29:12 -0000	1.7
  +++ mbeans-descriptors.xml	19 Nov 2004 16:46:26 -0000	1.8
  @@ -51,6 +51,67 @@
                  type="java.lang.String"/>
     </mbean>
   
  +  <mbean name="ByteBufferAccessLogValve"
  +           description="Valve that generates asynchronously web server access log"
  +           domain="Catalina"
  +           group="Valve"
  +           type="org.apache.catalina.valves.ByteBufferAccessLogValve">
  +  
  +      <attribute name="className"
  +                 description="Fully qualified class name of the managed object"
  +                 type="java.lang.String"
  +                 writeable="false"/>
  +  
  +      <attribute name="containerName"
  +                 description="Object name of the container"
  +                 type="javax.management.ObjectName"/>
  +  
  +      <attribute name="directory"
  +                 description="The directory in which log files are created"
  +                 type="java.lang.String"/>
  +  
  +      <attribute   name="pattern"
  +                 description="The pattern used to format our access log lines, which must
be either common or combined"
  +                 type="java.lang.String"/>
  +  
  +      <attribute name="prefix"
  +                 description="The prefix that is added to log file filenames"
  +                 type="java.lang.String"/>
  +  
  +      <attribute name="resolveHosts"
  +                 description="Resolve hosts"
  +                 is="true"
  +                 type="boolean"/>
  +  
  +      <attribute name="rotatable"
  +                 description="Flag to indicate automatic log rotation."
  +                 is="true"
  +                 type="boolean"/>
  +  
  +      <attribute name="suffix"
  +                 description="The suffix that is added to log file filenames"
  +                 type="java.lang.String"/>
  +  
  +      <attribute name="condition"
  +                 description="The value to look for conditional logging."
  +                 type="java.lang.String"/>
  +  
  +      <attribute name="fileDateFormat"
  +                 description="The format for the date date based log rotation."
  +                 type="java.lang.String"/>
  +                 
  +      <attribute name="directByteBufferSize"
  +                 description="The size of the byte buffer used to store the log."
  +                 type="java.lang.String"/>
  +   
  +      <attribute name="writeInterval"
  +               description="The interval (in seconds) between writting the log."
  +               type="int"/>
  +      
  +    </mbean>
  +  
  +  
  +  
     <mbean name="ErrorReportValve"
            description="Implementation of a Valve that outputs HTML error pages"
            domain="Catalina"
  
  
  
  1.1                  jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/valves/ByteBufferAccessLogValve.java
  
  Index: ByteBufferAccessLogValve.java
  ===================================================================
  /*
   * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
   * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
   */
  
  /*
   * Copyright 1999-2001,2004 The Apache Software Foundation.
   * 
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  
  package org.apache.catalina.valves;
  
  
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.FileOutputStream;
  import java.net.InetAddress;
  import java.nio.ByteBuffer;
  import java.nio.CharBuffer;
  import java.nio.BufferOverflowException;
  import java.nio.channels.FileChannel;
  import java.text.SimpleDateFormat;
  import java.text.DecimalFormat;
  import java.util.Date;
  import java.util.TimeZone;
  
  import javax.servlet.ServletException;
  
  import org.apache.catalina.Lifecycle;
  import org.apache.catalina.LifecycleException;
  import org.apache.catalina.LifecycleListener;
  import org.apache.catalina.connector.Request;
  import org.apache.catalina.connector.Response;
  import org.apache.catalina.util.LifecycleSupport;
  import org.apache.catalina.util.StringManager;
  
  
  /**
   * <p>Implementation of the <b>Valve</b> interface that generates a web
server
   * access log with the detailed line contents matching either the common or
   * combined patterns.  As an additional feature, automatic rollover of log files
   * when the date changes is also supported.</p>
   * <p>
   * Conditional logging is also supported. This can be done with the
   * <code>condition</code> property.
   * If the value returned from ServletRequest.getAttribute(condition)
   * yields a non-null value. The logging will be skipped.
   * </p>
   *
   * This us an NIO adaptation of the default AccessLogValve. 
   * 
   * <IMPLEMENTATION NOTE>
   *      The backgroundProcessorDelay is currently not used when flusing the
   *      <code>ByteBuffer</code> since setting that value affect all others
   *      component.
   *
   *      An AccessWriterThread is used instead, which times out after 5 minutes.
   *      
   * </IMPLEMENTATIOn NOTE>
   *
   * @author Jean-Francois Arcand
   */
  
  public final class ByteBufferAccessLogValve
      extends ValveBase
      implements Lifecycle, Runnable {
  
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new instance of this class with default property values.
       */
      public ByteBufferAccessLogValve() {
  
          super();
          setPattern("common");
          ByteBuffer byteBuffer = ByteBuffer.allocateDirect(directByteBufferSize);
          
          // setup first view buffer
          byteBuffer.position(0).limit(directByteBufferSize/2);
          viewBuffer1 = byteBuffer.slice();
  
          // setup 2nd view buffer
          byteBuffer.limit(byteBuffer.capacity()).position(directByteBufferSize/2);
          viewBuffer2 = byteBuffer.slice();
  
          currentByteBuffer = viewBuffer1;      
          writerByteBuffer = viewBuffer1;
   
      }
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The as-of date for the currently open log file, or a zero-length
       * string if there is no open log file.
       */
      private String dateStamp = "";
  
  
      /**
       * The directory in which log files are created.
       */
      private String directory = "logs";
  
  
      /**
       * The descriptive information about this implementation.
       */
      protected static final String info =
          "org.apache.catalina.valves.AccessLogValve/1.0";
  
  
      /**
       * The lifecycle event support for this component.
       */
      protected LifecycleSupport lifecycle = new LifecycleSupport(this);
  
  
      /**
       * The set of month abbreviations for log messages.
       */
      protected static final String months[] =
      { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  
  
      /**
       * If the current log pattern is the same as the common access log
       * format pattern, then we'll set this variable to true and log in
       * a more optimal and hard-coded way.
       */
      private boolean common = false;
  
  
      /**
       * For the combined format (common, plus useragent and referer), we do
       * the same
       */
      private boolean combined = false;
  
  
      /**
       * The pattern used to format our access log lines.
       */
      private String pattern = null;
  
  
      /**
       * The prefix that is added to log file filenames.
       */
      private String prefix = "access_log.";
  
  
      /**
       * Should we rotate our log file? Default is true (like old behavior)
       */
      private boolean rotatable = true;
  
  
      /**
       * The string manager for this package.
       */
      private StringManager sm =
          StringManager.getManager(Constants.Package);
  
  
      /**
       * Has this component been started yet?
       */
      private boolean started = false;
  
  
      /**
       * The suffix that is added to log file filenames.
       */
      private String suffix = "";
  
  
      /**
       * A date formatter to format a Date into a date in the format
       * "yyyy-MM-dd".
       */
      private SimpleDateFormat dateFormatter = null;
  
  
      /**
       * A date formatter to format Dates into a day string in the format
       * "dd".
       */
      private SimpleDateFormat dayFormatter = null;
  
  
      /**
       * A date formatter to format a Date into a month string in the format
       * "MM".
       */
      private SimpleDateFormat monthFormatter = null;
  
  
      /**
       * Time taken formatter for 3 decimal places.
       */
       private DecimalFormat timeTakenFormatter = null;
  
  
      /**
       * A date formatter to format a Date into a year string in the format
       * "yyyy".
       */
      private SimpleDateFormat yearFormatter = null;
  
  
      /**
       * A date formatter to format a Date into a time in the format
       * "kk:mm:ss" (kk is a 24-hour representation of the hour).
       */
      private SimpleDateFormat timeFormatter = null;
  
  
      /**
       * The time zone relative to GMT.
       */
      private String timeZone = null;
  
      
      /**
       * The system time when we last updated the Date that this valve
       * uses for log lines.
       */
      private String currentDateString = null;
  
      
      /**
       * The instant where the date string was last updated.
       */
      private long currentDate = 0L;
  
  
      /**
       * When formatting log lines, we often use strings like this one (" ").
       */
      private String space = " ";
  
  
      /**
       * Resolve hosts.
       */
      private boolean resolveHosts = false;
  
  
      /**
       * Instant when the log daily rotation was last checked.
       */
      private long rotationLastChecked = 0L;
  
  
      /**
       * Are we doing conditional logging. default false.
       */
      private String condition = null;
  
  
      /**
       * Date format to place in log file name. Use at your own risk!
       */
      private String fileDateFormat = null;
      
      
      /**
       * The <code>FileChannel</code> used to write the access log.
       */
      protected FileChannel fileChannel;
      
      
      /**
       * The background writerThread.
       */
      private Thread writerThread = null;   
      
      
      /**
       * The background writerThread completion semaphore.
       */
      private boolean threadDone = false;
      
      
      /**
       * The <code>ByteBuffer</code> used to store the logs.
       */
      private ByteBuffer viewBuffer1;
  
      
      /**
       * The <code>ByteBuffer</code> used to store the logs.
       */
      private ByteBuffer viewBuffer2;
  
      
      
      /**
       * The <code>ByteBuffer</code> used to store the logs.
       */
      private ByteBuffer writerByteBuffer;
      
      
      /**
       * The <code>ByteBuffer</code> used to store the logs.
       */
      private ByteBuffer currentByteBuffer;
     
      
      /**
       * The default byte buffer size. Default is 16k
       */
      protected int directByteBufferSize = 16 * 1024;
      
      
      /**
       * The interval (in seconds) between writting the log.
       */
      private int writeInterval = 5 * 60;
      
      
      /**
       * Per-Thread <code>StringBuffer</code> used to store the log.
       */
      static private ThreadLocal stringBufferThreadLocal = new ThreadLocal(){
            protected Object initialValue(){
                return new StringBuffer(80);
            }
      };
  
      
      /**
       * Per-Thread <code>StringBuffer</code> used to store the date.
       */
      static private ThreadLocal dateThreadLocal = new ThreadLocal(){
            protected Object initialValue(){
                return new StringBuffer(24);
            }
      };
      
      
      /**
       * Useful object to synchronize on.
       */
      private Object[] lock = new Object[0];
  
      
      /**
       * Set the direct <code>ByteBuffer</code> size
       */
      public void setBufferSize(int size){
          directByteBufferSize = size;
      }
      
      
      /**
       * Return the direct <code>ByteBuffer</code> size
       */    
      public int getBufferSize(){
          return directByteBufferSize;
      }
      
      
      /**
       * Return writerThread interval (seconds)
       */
      public int getWriterInterval() {        
          return this.writeInterval;     
      }
  
          
      /**
       * Set writerthread interval (seconds)
       */
      public void setWriterInterval(int t) {
          this.writeInterval = t;
      }
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Return the directory in which we create log files.
       */
      public String getDirectory() {
  
          return (directory);
  
      }
  
  
      /**
       * Set the directory in which we create log files.
       *
       * @param directory The new log file directory
       */
      public void setDirectory(String directory) {
  
          this.directory = directory;
  
      }
  
  
      /**
       * Return descriptive information about this implementation.
       */
      public String getInfo() {
  
          return (this.info);
  
      }
  
  
      /**
       * Return the format pattern.
       */
      public String getPattern() {
  
          return (this.pattern);
  
      }
  
  
      /**
       * Set the format pattern, first translating any recognized alias.
       *
       * @param pattern The new pattern
       */
      public void setPattern(String pattern) {
  
          if (pattern == null)
              pattern = "";
          if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
              pattern = Constants.AccessLog.COMMON_PATTERN;
          if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
              pattern = Constants.AccessLog.COMBINED_PATTERN;
          this.pattern = pattern;
  
          if (this.pattern.equals(Constants.AccessLog.COMMON_PATTERN))
              common = true;
          else
              common = false;
  
          if (this.pattern.equals(Constants.AccessLog.COMBINED_PATTERN))
              combined = true;
          else
              combined = false;
  
      }
  
  
      /**
       * Return the log file prefix.
       */
      public String getPrefix() {
  
          return (prefix);
  
      }
  
  
      /**
       * Set the log file prefix.
       *
       * @param prefix The new log file prefix
       */
      public void setPrefix(String prefix) {
  
          this.prefix = prefix;
  
      }
  
  
      /**
       * Should we rotate the logs
       */
      public boolean isRotatable() {
  
          return rotatable;
  
      }
  
  
      /**
       * Set the value is we should we rotate the logs
       *
       * @param rotatable true is we should rotate.
       */
      public void setRotatable(boolean rotatable) {
  
          this.rotatable = rotatable;
  
      }
  
  
      /**
       * Return the log file suffix.
       */
      public String getSuffix() {
  
          return (suffix);
  
      }
  
  
      /**
       * Set the log file suffix.
       *
       * @param suffix The new log file suffix
       */
      public void setSuffix(String suffix) {
  
          this.suffix = suffix;
  
      }
  
  
      /**
       * Set the resolve hosts flag.
       *
       * @param resolveHosts The new resolve hosts value
       */
      public void setResolveHosts(boolean resolveHosts) {
  
          this.resolveHosts = resolveHosts;
  
      }
  
  
      /**
       * Get the value of the resolve hosts flag.
       */
      public boolean isResolveHosts() {
  
          return resolveHosts;
  
      }
  
  
      /**
       * Return whether the attribute name to look for when
       * performing conditional loggging. If null, every
       * request is logged.
       */
      public String getCondition() {
  
          return condition;
  
      }
  
  
      /**
       * Set the ServletRequest.attribute to look for to perform
       * conditional logging. Set to null to log everything.
       *
       * @param condition Set to null to log everything
       */
      public void setCondition(String condition) {
  
          this.condition = condition;
  
      }
  
      /**
       *  Return the date format date based log rotation.
       */
      public String getFileDateFormat() {
          return fileDateFormat;
      }
  
  
      /**
       *  Set the date format date based log rotation.
       */
      public void setFileDateFormat(String fileDateFormat) {
          this.fileDateFormat =  fileDateFormat;
      }    
      
      
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Log a message summarizing the specified request and response, according
       * to the format specified by the <code>pattern</code> property.
       *
       * @param request Request being processed
       * @param response Response being processed
       * @param context The valve context used to invoke the next valve
       *  in the current processing pipeline
       *
       * @exception IOException if an input/output error has occurred
       * @exception ServletException if a servlet error has occurred
       */
     public void invoke(Request request, Response response)
              throws IOException, ServletException {
              
          // Pass this request on to the next valve in our pipeline
          getNext().invoke(request, response);
          
          if (condition!=null &&
                  null!=request.getRequest().getAttribute(condition)) {
              return;
          }
  
          // Check to see if we should log using the "common" access log pattern
          String value = null;
          StringBuffer stringBuffer = (StringBuffer)stringBufferThreadLocal.get();       
          stringBuffer.setLength(0);                                                     
                          
  
          if (isResolveHosts())
              stringBuffer.append(request.getRemoteHost());
          else
              stringBuffer.append(request.getRemoteAddr());
          
          stringBuffer.append(" - ");
          
          value = request.getRemoteUser();
          if (value == null)
              stringBuffer.append("- ");
          else {
              stringBuffer.append(value);
              stringBuffer.append(space);
          }
          
          stringBuffer.append(getCurrentDateString());
          
          stringBuffer.append(request.getMethod());
          stringBuffer.append(space);
          stringBuffer.append(request.getRequestURI());
          if (request.getQueryString() != null) {
              stringBuffer.append('?');
              stringBuffer.append(request.getQueryString());
          }
          stringBuffer.append(space);
          stringBuffer.append(request.getProtocol());
          stringBuffer.append("\" ");
          
          stringBuffer.append(response.getStatus());
          
          stringBuffer.append(space);
          
          int length = response.getContentCount();
          
          if (length <= 0)
              value = "-";
          else
              value = "" + length;
          stringBuffer.append(value);
          
          if (combined) {
              stringBuffer.append(space);
              stringBuffer.append("\"");
              String referer = request.getHeader("referer");
              if(referer != null)
                  stringBuffer.append(referer);
              else
                  stringBuffer.append("-");
              stringBuffer.append("\"");
              
              stringBuffer.append(space);
              stringBuffer.append("\"");
              String ua = request.getHeader("user-agent");
              if(ua != null)
                  stringBuffer.append(ua);
              else
                  stringBuffer.append("-");
              stringBuffer.append("\"");
          }
          stringBuffer.append("\n");
          
          // synchronized before switching buffer
          byte[] bytesToWrite = stringBuffer.toString().getBytes();
          synchronized (lock) {
              int remaining = currentByteBuffer.remaining();
              boolean flushBuffer = false;
              if ( remaining < bytesToWrite.length){
                  // The byteBuffer is full, so we need to
                  // flush the buffer.
                  flushBuffer = true;
              } else {
                  currentByteBuffer.put(bytesToWrite);
              }
  
  
              // The buffer is full, so flush it.
              if ( flushBuffer ){
                  if (currentByteBuffer == viewBuffer1) {
                      writerByteBuffer = viewBuffer1;
                  } else {
                      writerByteBuffer = viewBuffer2;
                  }
  
                  try{
                      lock.notify();
                  } catch (Throwable t) {
                      t.printStackTrace();
                  }
                  
                  if (currentByteBuffer == viewBuffer1) {
                      currentByteBuffer = viewBuffer2;
                  } else {
                      currentByteBuffer = viewBuffer1;
                  }
                  // Now we can store the results since the currentCharBuffer 
                  // is empty.
                  currentByteBuffer.put(bytesToWrite);
              }
  
          }
         
      }
  
  
      // -------------------------------------------------------- Private Methods
  
     
      /**
       * This method returns a Date object that is accurate to within one
       * second.  If a thread calls this method to get a Date and it's been
       * less than 1 second since a new Date was created, this method
       * simply gives out the same Date again so that the system doesn't
       * spend time creating Date objects unnecessarily.
       *
       * @return Date
       */
      private String getCurrentDateString() {
          // Only create a new Date once per second, max.
          long systime = System.currentTimeMillis();
          if ((systime - currentDate) > 1000) {
              synchronized (this) {
                  // We don't care about being exact here: if an entry does get
                  // logged as having happened during the previous second
                  // it will not make any difference
                  if ((systime - currentDate) > 1000) {
  
                      // Format the new date
                      Date date = new Date();
                      StringBuffer result = (StringBuffer)dateThreadLocal.get();       
                      result.setLength(0);  
                      
                      result.append("[");
                      // Day
                      result.append(dayFormatter.format(date));
                      result.append('/');
                      // Month
                      result.append(lookup(monthFormatter.format(date)));
                      result.append('/');
                      // Year
                      result.append(yearFormatter.format(date));
                      result.append(':');
                      // Time
                      result.append(timeFormatter.format(date));
                      result.append(space);
                      // Time zone
                      result.append(timeZone);
                      result.append("] \"");
                      
                      // Check for log rotation
                      if (rotatable) {
                          // Check for a change of date
                          String tsDate = dateFormatter.format(date);
                          // If the date has changed, switch log files
                          if (!dateStamp.equals(tsDate)) {
                              synchronized (this) {
                                  if (!dateStamp.equals(tsDate)) {
                                      close();
                                      dateStamp = tsDate;
                                      open();
                                  }
                              }
                          }
                      }
                      
                      currentDateString = result.toString();
                      currentDate = date.getTime();
                  }
              }
          }
          return currentDateString;
      }
      
      
      /**
       * Close the currently open log file (if any)
       */
      private synchronized void close() {
  
          dateStamp = "";
          
          // Make sure everything has been writen.
          synchronized(lock){
              log();
          }
      }
  
  
      /**
       * Log the specified message to the log file, switching files if the date
       * has changed since the previous log call.
       *
       * @param message Message to be logged
       * @param date the current Date object (so this method doesn't need to
       *        create a new one)
       */
      public void log() {        
          try{
              writerByteBuffer.flip();
   
              while (writerByteBuffer.hasRemaining()){
                  fileChannel.write(writerByteBuffer);
              }
              writerByteBuffer.clear();
          } catch (IOException ex){
              ;
          }
  
      }
  
  
      /**
       * Return the month abbreviation for the specified month, which must
       * be a two-digit String.
       *
       * @param month Month number ("01" .. "12").
       */
      private String lookup(String month) {
  
          int index;
          try {
              index = Integer.parseInt(month) - 1;
          } catch (Throwable t) {
              index = 0;  // Can not happen, in theory
          }
          return (months[index]);
  
      }
  
  
      /**
       * Open the new log file for the date specified by <code>dateStamp</code>.
       */
      private synchronized void open() {
  
          // Create the directory if necessary
          File dir = new File(directory);
          if (!dir.isAbsolute())
              dir = new File(System.getProperty("catalina.base"), directory);
          dir.mkdirs();
  
          // Open the current log file
          try {
              String pathname;
              // If no rotate - no need for dateStamp in fileName
              if (rotatable){
                  pathname = dir.getAbsolutePath() + File.separator +
                              prefix + dateStamp + suffix;
              } else {
                  pathname = dir.getAbsolutePath() + File.separator +
                              prefix + suffix;
              }
              
              // Open the file and then get a channel from the stream
              FileOutputStream fis = new FileOutputStream(pathname, true);
              fileChannel = fis.getChannel();
  
          } catch (IOException e) {
              try{
                  fileChannel.close();
              } catch (IOException ex){
                  ;
              }
          } 
  
      }
  
  
      private String calculateTimeZoneOffset(long offset) {
          StringBuffer tz = new StringBuffer();
          if ((offset<0))  {
              tz.append("-");
              offset = -offset;
          } else {
              tz.append("+");
          }
  
          long hourOffset = offset/(1000*60*60);
          long minuteOffset = (offset/(1000*60)) % 60;
  
          if (hourOffset<10)
              tz.append("0");
          tz.append(hourOffset);
  
          if (minuteOffset<10)
              tz.append("0");
          tz.append(minuteOffset);
  
          return tz.toString();
      }
  
  
      // ------------------------------------------------------ Lifecycle Methods
  
  
      /**
       * Add a lifecycle event listener to this component.
       *
       * @param listener The listener to add
       */
      public void addLifecycleListener(LifecycleListener listener) {
  
          lifecycle.addLifecycleListener(listener);
  
      }
  
  
      /**
       * Get the lifecycle listeners associated with this lifecycle. If this
       * Lifecycle has no listeners registered, a zero-length array is returned.
       */
      public LifecycleListener[] findLifecycleListeners() {
  
          return lifecycle.findLifecycleListeners();
  
      }
  
  
      /**
       * Remove a lifecycle event listener from this component.
       *
       * @param listener The listener to add
       */
      public void removeLifecycleListener(LifecycleListener listener) {
  
          lifecycle.removeLifecycleListener(listener);
  
      }
  
  
     /**
       * Prepare for the beginning of active use of the public methods of this
       * component.  This method should be called after <code>configure()</code>,
       * and before any of the public methods of the component are utilized.
       *
       * @exception LifecycleException if this component detects a fatal error
       *  that prevents this component from being used
       */
      public void start() throws LifecycleException {
  
          // Validate and update our current component state
          if (started)
              throw new LifecycleException
                  (sm.getString("accessLogValve.alreadyStarted"));
          lifecycle.fireLifecycleEvent(START_EVENT, null);
          started = true;
  
          // Initialize the timeZone, Date formatters, and currentDate
          TimeZone tz = TimeZone.getDefault();
          timeZone = calculateTimeZoneOffset(tz.getRawOffset());
  
          if (fileDateFormat==null || fileDateFormat.length()==0)
              fileDateFormat = "yyyy-MM-dd";
          dateFormatter = new SimpleDateFormat(fileDateFormat);
          dateFormatter.setTimeZone(tz);
          dayFormatter = new SimpleDateFormat("dd");
          dayFormatter.setTimeZone(tz);
          monthFormatter = new SimpleDateFormat("MM");
          monthFormatter.setTimeZone(tz);
          yearFormatter = new SimpleDateFormat("yyyy");
          yearFormatter.setTimeZone(tz);
          timeFormatter = new SimpleDateFormat("HH:mm:ss");
          timeFormatter.setTimeZone(tz);
          currentDateString = getCurrentDateString();
          dateStamp = dateFormatter.format(new Date());
  
          open();
          
          threadStart();
  
      }
  
  
      /**
       * Gracefully terminate the active use of the public methods of this
       * component.  This method should be the last one called on a given
       * instance of this component.
       *
       * @exception LifecycleException if this component detects a fatal error
       *  that needs to be reported
       */
      public void stop() throws LifecycleException {
  
          // Validate and update our current component state
          if (!started)
              throw new LifecycleException
                  (sm.getString("accessLogValve.notStarted"));
          lifecycle.fireLifecycleEvent(STOP_EVENT, null);
          started = false;
  
          close();
          
          threadStop();
  
      }
      
      
      /**
       * Execute a periodic task, such as reloading, etc. This method will be
       * invoked inside the classloading context of this container. Unexpected
       * throwables will be caught and logged.
       */
      public void backgroundProcess() {
          // XXX Shoud the backgroud thread be used here?
          // log();
      }
      
      
      /**
       * The background writerThread that checks for write the log.
       */
      public void run() {
  
          // Loop until the termination semaphore is set
          while (!threadDone) {
              threadWait();
              log();
          }
      }
      
      
      /**
       * Sleep for the duration specified by the <code>writeInterval</code>
       * property.
       */
      private void threadWait() {
  
          synchronized(lock){
              try {
                  lock.wait(writeInterval * 1000L);
              } catch (InterruptedException e) {
                  ;
              }
          }
  
      }
  
          
     /**
       * Start the background writerThread that will periodically write access log
       */
      private void threadStart() {
  
          if (writerThread != null)
              return;
  
          threadDone = false;
          String threadName = "AccessLogWriter";
          writerThread = new Thread(this, threadName);
          writerThread.setDaemon(true);
          writerThread.start();
  
      }
  
  
      /**
       * Stop the background writerThread that is periodically write logs
       */
      private void threadStop() {
  
          if (writerThread == null)
              return;
  
          threadDone = true;
          writerThread.interrupt();
          try {
              writerThread.join();
          } catch (InterruptedException e) {
              ;
          }
  
          writerThread = null;
  
      }
      
      
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-dev-help@jakarta.apache.org


Mime
View raw message